@@ -10,13 +10,15 @@ import { DateTime } from "luxon"
1010
1111interface PartBase {
1212 content : string
13+ start : number
14+ end : number
1315}
1416
15- interface TextPart extends PartBase {
17+ export interface TextPart extends PartBase {
1618 type : "text"
1719}
1820
19- interface FileAttachmentPart extends PartBase {
21+ export interface FileAttachmentPart extends PartBase {
2022 type : "file"
2123 path : string
2224 selection ?: TextSelection
@@ -34,7 +36,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
3436 const local = useLocal ( )
3537 let editorRef ! : HTMLDivElement
3638
37- const defaultParts = [ { type : "text" , content : "" } as const ]
39+ const defaultParts = [ { type : "text" , content : "" , start : 0 , end : 0 } as const ]
3840 const [ store , setStore ] = createStore < {
3941 contentParts : ContentPart [ ]
4042 popoverIsOpen : boolean
@@ -51,7 +53,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
5153 event . stopPropagation ( )
5254 // @ts -expect-error
5355 const plainText = ( event . clipboardData || window . clipboardData ) ?. getData ( "text/plain" ) ?? ""
54- addPart ( { type : "text" , content : plainText } )
56+ addPart ( { type : "text" , content : plainText , start : 0 , end : 0 } )
5557 }
5658
5759 onMount ( ( ) => {
@@ -74,7 +76,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
7476 key : ( x ) => x ,
7577 onSelect : ( path ) => {
7678 if ( ! path ) return
77- addPart ( { type : "file" , path, content : "@" + getFilename ( path ) } )
79+ addPart ( { type : "file" , path, content : "@" + getFilename ( path ) , start : 0 , end : 0 } )
7880 setStore ( "popoverIsOpen" , false )
7981 } ,
8082 } )
@@ -117,17 +119,26 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
117119
118120 const parseFromDOM = ( ) : ContentPart [ ] => {
119121 const newParts : ContentPart [ ] = [ ]
122+ let position = 0
120123 editorRef . childNodes . forEach ( ( node ) => {
121124 if ( node . nodeType === Node . TEXT_NODE ) {
122- if ( node . textContent ) newParts . push ( { type : "text" , content : node . textContent } )
125+ if ( node . textContent ) {
126+ const content = node . textContent
127+ newParts . push ( { type : "text" , content, start : position , end : position + content . length } )
128+ position += content . length
129+ }
123130 } else if ( node . nodeType === Node . ELEMENT_NODE && ( node as HTMLElement ) . dataset . type ) {
124131 switch ( ( node as HTMLElement ) . dataset . type ) {
125132 case "file" :
133+ const content = node . textContent !
126134 newParts . push ( {
127135 type : "file" ,
128136 path : ( node as HTMLElement ) . dataset . path ! ,
129- content : node . textContent ! ,
137+ content,
138+ start : position ,
139+ end : position + content . length ,
130140 } )
141+ position += content . length
131142 break
132143 default :
133144 break
@@ -163,17 +174,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
163174 const startIndex = atMatch ? atMatch . index ! : cursorPosition
164175 const endIndex = atMatch ? cursorPosition : cursorPosition
165176
166- const pushText = ( acc : { parts : ContentPart [ ] } , value : string ) => {
177+ const pushText = ( acc : { parts : ContentPart [ ] ; runningIndex : number } , value : string ) => {
167178 if ( ! value ) return
168179 const last = acc . parts [ acc . parts . length - 1 ]
169180 if ( last && last . type === "text" ) {
170181 acc . parts [ acc . parts . length - 1 ] = {
171182 type : "text" ,
172183 content : last . content + value ,
184+ start : last . start ,
185+ end : last . end + value . length ,
173186 }
174187 return
175188 }
176- acc . parts . push ( { type : "text" , content : value } )
189+ acc . parts . push ( { type : "text" , content : value , start : acc . runningIndex , end : acc . runningIndex + value . length } )
177190 }
178191
179192 const {
@@ -183,20 +196,20 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
183196 } = store . contentParts . reduce (
184197 ( acc , item ) => {
185198 if ( acc . inserted ) {
186- acc . parts . push ( item )
199+ acc . parts . push ( { ... item , start : acc . runningIndex , end : acc . runningIndex + item . content . length } )
187200 acc . runningIndex += item . content . length
188201 return acc
189202 }
190203
191204 const nextIndex = acc . runningIndex + item . content . length
192205 if ( nextIndex <= startIndex ) {
193- acc . parts . push ( item )
206+ acc . parts . push ( { ... item , start : acc . runningIndex , end : acc . runningIndex + item . content . length } )
194207 acc . runningIndex = nextIndex
195208 return acc
196209 }
197210
198211 if ( item . type !== "text" ) {
199- acc . parts . push ( item )
212+ acc . parts . push ( { ... item , start : acc . runningIndex , end : acc . runningIndex + item . content . length } )
200213 acc . runningIndex = nextIndex
201214 return acc
202215 }
@@ -207,24 +220,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
207220 const tail = item . content . slice ( tailLength )
208221
209222 pushText ( acc , head )
223+ acc . runningIndex += head . length
210224
211225 if ( part . type === "text" ) {
212226 pushText ( acc , part . content )
227+ acc . runningIndex += part . content . length
213228 }
214229 if ( part . type !== "text" ) {
215- acc . parts . push ( { ...part } )
230+ acc . parts . push ( { ...part , start : acc . runningIndex , end : acc . runningIndex + part . content . length } )
231+ acc . runningIndex += part . content . length
216232 }
217233
218234 const needsGap = Boolean ( atMatch )
219235 const rest = needsGap ? ( tail ? ( / ^ \s / . test ( tail ) ? tail : ` ${ tail } ` ) : " " ) : tail
220236 pushText ( acc , rest )
237+ acc . runningIndex += rest . length
221238
222239 const baseCursor = startIndex + part . content . length
223240 const cursorAddition = needsGap && rest . length > 0 ? 1 : 0
224241 acc . cursorPositionAfter = baseCursor + cursorAddition
225242
226243 acc . inserted = true
227- acc . runningIndex = nextIndex
228244 return acc
229245 } ,
230246 {
@@ -237,9 +253,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
237253
238254 if ( ! inserted ) {
239255 const baseParts = store . contentParts . filter ( ( item ) => ! ( item . type === "text" && item . content === "" ) )
240- const appendedAcc = { parts : [ ...baseParts ] as ContentPart [ ] }
241- if ( part . type === "text" ) pushText ( appendedAcc , part . content )
242- if ( part . type !== "text" ) appendedAcc . parts . push ( { ...part } )
256+ const runningIndex = baseParts . reduce ( ( sum , p ) => sum + p . content . length , 0 )
257+ const appendedAcc = { parts : [ ...baseParts ] as ContentPart [ ] , runningIndex }
258+ if ( part . type === "text" ) {
259+ pushText ( appendedAcc , part . content )
260+ }
261+ if ( part . type !== "text" ) {
262+ appendedAcc . parts . push ( {
263+ ...part ,
264+ start : appendedAcc . runningIndex ,
265+ end : appendedAcc . runningIndex + part . content . length ,
266+ } )
267+ }
243268 const next = appendedAcc . parts . length > 0 ? appendedAcc . parts : defaultParts
244269 setStore ( "contentParts" , next )
245270 setStore ( "popoverIsOpen" , false )
0 commit comments