@@ -124,18 +124,20 @@ export function Prompt(props: PromptProps) {
124124 const [ store , setStore ] = createStore < {
125125 prompt : PromptInfo
126126 mode : "normal" | "shell"
127- extmarkToPartIndex : Map < number , number >
127+ partByExtmark : Map < number , number >
128128 interrupt : number
129129 placeholder : number
130+ expanded : Set < number >
130131 } > ( {
131132 placeholder : Math . floor ( Math . random ( ) * PLACEHOLDERS . length ) ,
132133 prompt : {
133134 input : "" ,
134135 parts : [ ] ,
135136 } ,
136137 mode : "normal" ,
137- extmarkToPartIndex : new Map ( ) ,
138+ partByExtmark : new Map ( ) ,
138139 interrupt : 0 ,
140+ expanded : new Set ( ) ,
139141 } )
140142
141143 createEffect (
@@ -179,6 +181,8 @@ export function Prompt(props: PromptProps) {
179181 onSelect : ( dialog ) => {
180182 input . extmarks . clear ( )
181183 input . clear ( )
184+ setStore ( "partByExtmark" , new Map ( ) )
185+ setStore ( "expanded" , new Set ( ) )
182186 dialog . clear ( )
183187 } ,
184188 } ,
@@ -381,7 +385,8 @@ export function Prompt(props: PromptProps) {
381385 input : "" ,
382386 parts : [ ] ,
383387 } )
384- setStore ( "extmarkToPartIndex" , new Map ( ) )
388+ setStore ( "partByExtmark" , new Map ( ) )
389+ setStore ( "expanded" , new Set ( ) )
385390 } ,
386391 submit ( ) {
387392 submit ( )
@@ -395,7 +400,8 @@ export function Prompt(props: PromptProps) {
395400
396401 function restoreExtmarksFromParts ( parts : PromptInfo [ "parts" ] ) {
397402 input . extmarks . clear ( )
398- setStore ( "extmarkToPartIndex" , new Map ( ) )
403+ setStore ( "partByExtmark" , new Map ( ) )
404+ setStore ( "expanded" , new Set ( ) )
399405
400406 parts . forEach ( ( part , partIndex ) => {
401407 let start = 0
@@ -416,7 +422,7 @@ export function Prompt(props: PromptProps) {
416422 } else if ( part . type === "text" && part . source ?. text ) {
417423 start = part . source . text . start
418424 end = part . source . text . end
419- virtualText = part . source . text . value
425+ virtualText = part . source . text . value || summary ( part . text )
420426 styleId = pasteStyleId
421427 }
422428
@@ -427,8 +433,9 @@ export function Prompt(props: PromptProps) {
427433 virtual : true ,
428434 styleId,
429435 typeId : promptPartTypeId ,
436+ onClick : part . type === "text" ? ( id ) => toggle ( id ) : undefined ,
430437 } )
431- setStore ( "extmarkToPartIndex " , ( map : Map < number , number > ) => {
438+ setStore ( "partByExtmark " , ( map : Map < number , number > ) => {
432439 const newMap = new Map ( map )
433440 newMap . set ( extmarkId , partIndex )
434441 return newMap
@@ -445,7 +452,7 @@ export function Prompt(props: PromptProps) {
445452 const newParts : typeof draft . prompt . parts = [ ]
446453
447454 for ( const extmark of allExtmarks ) {
448- const partIndex = draft . extmarkToPartIndex . get ( extmark . id )
455+ const partIndex = draft . partByExtmark . get ( extmark . id )
449456 if ( partIndex !== undefined ) {
450457 const part = draft . prompt . parts [ partIndex ]
451458 if ( part ) {
@@ -458,14 +465,17 @@ export function Prompt(props: PromptProps) {
458465 } else if ( part . type === "text" && part . source ?. text ) {
459466 part . source . text . start = extmark . start
460467 part . source . text . end = extmark . end
468+ if ( draft . expanded . has ( extmark . id ) ) {
469+ part . text = read ( part , extmark )
470+ }
461471 }
462472 newMap . set ( extmark . id , newParts . length )
463473 newParts . push ( part )
464474 }
465475 }
466476 }
467477
468- draft . extmarkToPartIndex = newMap
478+ draft . partByExtmark = newMap
469479 draft . prompt . parts = newParts
470480 } ) ,
471481 )
@@ -486,7 +496,8 @@ export function Prompt(props: PromptProps) {
486496 input . extmarks . clear ( )
487497 input . clear ( )
488498 setStore ( "prompt" , { input : "" , parts : [ ] } )
489- setStore ( "extmarkToPartIndex" , new Map ( ) )
499+ setStore ( "partByExtmark" , new Map ( ) )
500+ setStore ( "expanded" , new Set ( ) )
490501 dialog . clear ( )
491502 } ,
492503 } ,
@@ -569,7 +580,7 @@ export function Prompt(props: PromptProps) {
569580 const sortedExtmarks = allExtmarks . sort ( ( a : { start : number } , b : { start : number } ) => b . start - a . start )
570581
571582 for ( const extmark of sortedExtmarks ) {
572- const partIndex = store . extmarkToPartIndex . get ( extmark . id )
583+ const partIndex = store . partByExtmark . get ( extmark . id )
573584 if ( partIndex !== undefined ) {
574585 const part = store . prompt . parts [ partIndex ]
575586 if ( part ?. type === "text" && part . text ) {
@@ -660,7 +671,8 @@ export function Prompt(props: PromptProps) {
660671 input : "" ,
661672 parts : [ ] ,
662673 } )
663- setStore ( "extmarkToPartIndex" , new Map ( ) )
674+ setStore ( "partByExtmark" , new Map ( ) )
675+ setStore ( "expanded" , new Set ( ) )
664676 props . onSubmit ?.( )
665677
666678 // temporary hack to make sure the message is sent
@@ -688,6 +700,7 @@ export function Prompt(props: PromptProps) {
688700 virtual : true ,
689701 styleId : pasteStyleId ,
690702 typeId : promptPartTypeId ,
703+ onClick : ( id ) => toggle ( id ) ,
691704 } )
692705
693706 setStore (
@@ -704,11 +717,92 @@ export function Prompt(props: PromptProps) {
704717 } ,
705718 } ,
706719 } )
707- draft . extmarkToPartIndex . set ( extmarkId , partIndex )
720+ draft . partByExtmark . set ( extmarkId , partIndex )
708721 } ) ,
709722 )
710723 }
711724
725+ function lines ( text : string ) {
726+ return ( text . match ( / \n / g) ?. length ?? 0 ) + 1
727+ }
728+
729+ function summary ( text : string ) {
730+ return `[Pasted ~${ lines ( text ) } lines]`
731+ }
732+
733+ function read ( part : Extract < PromptInfo [ "parts" ] [ number ] , { type : "text" } > , extmark : { start : number ; end : number } ) {
734+ if ( ! part . source ?. text ) return part . text
735+ return input . plainText . slice ( extmark . start , extmark . end )
736+ }
737+
738+ function toggle ( id : number ) {
739+ if ( ! input || input . isDestroyed ) return
740+
741+ const idx = store . partByExtmark . get ( id )
742+ if ( idx === undefined ) return
743+
744+ const part = store . prompt . parts [ idx ]
745+ if ( ! part || part . type !== "text" || ! part . source ?. text ) return
746+
747+ const extmark = input . extmarks . get ( id )
748+ if ( ! extmark ) return
749+
750+ const start = extmark . start
751+ const end = extmark . end
752+ const viewport = input . editorView . getViewport ( )
753+ const cursor = input . visualCursor . offset
754+
755+ const open = store . expanded . has ( id )
756+ const next = open ? read ( part , extmark ) : part . text
757+ const tag = summary ( next )
758+ const text = open ? tag : next
759+ const cur =
760+ cursor < start
761+ ? cursor
762+ : cursor > end
763+ ? cursor + text . length - ( end - start )
764+ : Math . min ( start + text . length , cursor )
765+ const from = input . editBuffer . offsetToPosition ( start )
766+ const to = input . editBuffer . offsetToPosition ( end + 1 )
767+ if ( ! from || ! to ) return
768+
769+ input . extmarks . delete ( id )
770+ input . editBuffer . deleteRange ( from . row , from . col , to . row , to . col )
771+ input . editBuffer . setCursorByOffset ( start )
772+ input . editBuffer . insertText ( text + " " )
773+ const nextId = input . extmarks . create ( {
774+ start,
775+ end : start + text . length ,
776+ virtual : open ,
777+ styleId : pasteStyleId ,
778+ typeId : promptPartTypeId ,
779+ onClick : ( id ) => toggle ( id ) ,
780+ } )
781+
782+ setStore (
783+ produce ( ( draft ) => {
784+ draft . partByExtmark . delete ( id )
785+ draft . partByExtmark . set ( nextId , idx )
786+ const item = draft . prompt . parts [ idx ]
787+ if ( item ?. type === "text" && item . source ?. text ) {
788+ item . text = next
789+ item . source . text . value = tag
790+ }
791+ if ( draft . expanded . has ( id ) ) {
792+ draft . expanded . delete ( id )
793+ draft . expanded . delete ( nextId )
794+ } else {
795+ draft . expanded . add ( nextId )
796+ }
797+ } ) ,
798+ )
799+
800+ input . editBuffer . setCursorByOffset ( cur )
801+ input . editorView . setViewport ( viewport . offsetX , viewport . offsetY , viewport . width , viewport . height , false )
802+ input . getLayoutNode ( ) . markDirty ( )
803+ renderer . requestRender ( )
804+ }
805+
712806 async function pasteImage ( file : { filename ?: string ; content : string ; mime : string } ) {
713807 const currentOffset = input . visualCursor . offset
714808 const extmarkStart = currentOffset
@@ -746,7 +840,7 @@ export function Prompt(props: PromptProps) {
746840 produce ( ( draft ) => {
747841 const partIndex = draft . prompt . parts . length
748842 draft . prompt . parts . push ( part )
749- draft . extmarkToPartIndex . set ( extmarkId , partIndex )
843+ draft . partByExtmark . set ( extmarkId , partIndex )
750844 } ) ,
751845 )
752846 return
@@ -805,7 +899,7 @@ export function Prompt(props: PromptProps) {
805899 setStore ( "prompt" , produce ( cb ) )
806900 } }
807901 setExtmark = { ( partIndex , extmarkId ) => {
808- setStore ( "extmarkToPartIndex " , ( map : Map < number , number > ) => {
902+ setStore ( "partByExtmark " , ( map : Map < number , number > ) => {
809903 const newMap = new Map ( map )
810904 newMap . set ( extmarkId , partIndex )
811905 return newMap
@@ -876,7 +970,8 @@ export function Prompt(props: PromptProps) {
876970 input : "" ,
877971 parts : [ ] ,
878972 } )
879- setStore ( "extmarkToPartIndex" , new Map ( ) )
973+ setStore ( "partByExtmark" , new Map ( ) )
974+ setStore ( "expanded" , new Set ( ) )
880975 return
881976 }
882977 if ( keybind . match ( "app_exit" , e ) ) {
@@ -977,13 +1072,12 @@ export function Prompt(props: PromptProps) {
9771072 } catch { }
9781073 }
9791074
980- const lineCount = ( pastedContent . match ( / \n / g) ?. length ?? 0 ) + 1
9811075 if (
982- ( lineCount >= 3 || pastedContent . length > 150 ) &&
1076+ ( lines ( pastedContent ) >= 3 || pastedContent . length > 150 ) &&
9831077 ! sync . data . config . experimental ?. disable_paste_summary
9841078 ) {
9851079 event . preventDefault ( )
986- pasteText ( pastedContent , `[Pasted ~ ${ lineCount } lines]` )
1080+ pasteText ( pastedContent , summary ( pastedContent ) )
9871081 return
9881082 }
9891083
0 commit comments