11import type { BoxRenderable , TextareaRenderable , KeyEvent , ScrollBoxRenderable } from "@opentui/core"
22import { pathToFileURL } from "bun"
33import fuzzysort from "fuzzysort"
4+ import path from "path"
45import { firstBy } from "remeda"
56import { createMemo , createResource , createEffect , onMount , onCleanup , Index , Show , createSignal } from "solid-js"
67import { createStore } from "solid-js/store"
8+ import { useEditorContext } from "@tui/context/editor"
79import { useSDK } from "@tui/context/sdk"
810import { useSync } from "@tui/context/sync"
911import { getScrollAcceleration } from "../../util/scroll"
@@ -77,6 +79,7 @@ export function Autocomplete(props: {
7779 agentStyleId : number
7880 promptPartTypeId : ( ) => number
7981} ) {
82+ const editor = useEditorContext ( )
8083 const sdk = useSDK ( )
8184 const sync = useSync ( )
8285 const command = useCommandDialog ( )
@@ -221,6 +224,70 @@ export function Autocomplete(props: {
221224 }
222225 }
223226
227+ function createFilePart ( item : string , lineRange ?: { startLine : number ; endLine ?: number } ) {
228+ const baseDir = ( sync . path . directory || process . cwd ( ) ) . replace ( / \/ + $ / , "" )
229+ const fullPath = path . isAbsolute ( item ) ? item : path . join ( baseDir , item )
230+ const urlObj = pathToFileURL ( fullPath )
231+ const filename =
232+ lineRange && ! item . endsWith ( "/" )
233+ ? `${ item } #${ lineRange . startLine } ${ lineRange . endLine ? `-${ lineRange . endLine } ` : "" } `
234+ : item
235+
236+ if ( lineRange && ! item . endsWith ( "/" ) ) {
237+ urlObj . searchParams . set ( "start" , String ( lineRange . startLine ) )
238+ if ( lineRange . endLine !== undefined ) {
239+ urlObj . searchParams . set ( "end" , String ( lineRange . endLine ) )
240+ }
241+ }
242+
243+ return {
244+ filename,
245+ url : urlObj . href ,
246+ part : {
247+ type : "file" as const ,
248+ mime : "text/plain" ,
249+ filename,
250+ url : urlObj . href ,
251+ source : {
252+ type : "file" as const ,
253+ text : {
254+ start : 0 ,
255+ end : 0 ,
256+ value : "" ,
257+ } ,
258+ path : item ,
259+ } ,
260+ } ,
261+ }
262+ }
263+
264+ function normalizeMentionPath ( filePath : string ) {
265+ const baseDir = sync . path . directory || process . cwd ( )
266+ const absolute = path . resolve ( filePath )
267+ const relative = path . relative ( baseDir , absolute )
268+
269+ if ( relative && ! relative . startsWith ( ".." ) && ! path . isAbsolute ( relative ) ) {
270+ return relative . split ( path . sep ) . join ( "/" )
271+ }
272+
273+ return absolute . split ( path . sep ) . join ( "/" )
274+ }
275+
276+ function insertFileMention ( input : { filePath : string ; lineStart : number ; lineEnd : number } ) {
277+ const item = normalizeMentionPath ( input . filePath )
278+ const lineRange = {
279+ startLine : input . lineStart ,
280+ endLine : input . lineEnd > input . lineStart ? input . lineEnd : undefined ,
281+ }
282+ const { filename, part } = createFilePart ( item , lineRange )
283+ const index = store . visible === "@" ? store . index : props . input ( ) . cursorOffset
284+
285+ command . keybinds ( true )
286+ setStore ( "visible" , false )
287+ setStore ( "index" , index )
288+ insertPart ( filename , part )
289+ }
290+
224291 const [ files ] = createResource (
225292 ( ) => search ( ) ,
226293 async ( query ) => {
@@ -233,10 +300,10 @@ export function Autocomplete(props: {
233300 query : baseQuery ,
234301 } )
235302
236- const options : AutocompleteOption [ ] = [ ]
303+ const options : AutocompleteOption [ ] = [ ]
237304
238- // Add file options
239- if ( ! result . error && result . data ) {
305+ // Add file options
306+ if ( ! result . error && result . data ) {
240307 const sortedFiles = result . data . sort ( ( a , b ) => {
241308 const aScore = frecency . getFrecency ( a )
242309 const bScore = frecency . getFrecency ( b )
@@ -247,49 +314,24 @@ export function Autocomplete(props: {
247314 return a . localeCompare ( b )
248315 } )
249316
250- const width = props . anchor ( ) . width - 4
251- options . push (
252- ...sortedFiles . map ( ( item ) : AutocompleteOption => {
253- const baseDir = ( sync . path . directory || process . cwd ( ) ) . replace ( / \/ + $ / , "" )
254- const fullPath = `${ baseDir } /${ item } `
255- const urlObj = pathToFileURL ( fullPath )
256- let filename = item
257- if ( lineRange && ! item . endsWith ( "/" ) ) {
258- filename = `${ item } #${ lineRange . startLine } ${ lineRange . endLine ? `-${ lineRange . endLine } ` : "" } `
259- urlObj . searchParams . set ( "start" , String ( lineRange . startLine ) )
260- if ( lineRange . endLine !== undefined ) {
261- urlObj . searchParams . set ( "end" , String ( lineRange . endLine ) )
317+ const width = props . anchor ( ) . width - 4
318+ options . push (
319+ ...sortedFiles . map ( ( item ) : AutocompleteOption => {
320+ const { filename, url, part } = createFilePart ( item , lineRange )
321+
322+ const isDir = item . endsWith ( "/" )
323+ return {
324+ display : Locale . truncateMiddle ( filename , width ) ,
325+ value : filename ,
326+ isDirectory : isDir ,
327+ path : item ,
328+ onSelect : ( ) => {
329+ insertPart ( filename , part )
330+ } ,
262331 }
263- }
264- const url = urlObj . href
265-
266- const isDir = item . endsWith ( "/" )
267- return {
268- display : Locale . truncateMiddle ( filename , width ) ,
269- value : filename ,
270- isDirectory : isDir ,
271- path : item ,
272- onSelect : ( ) => {
273- insertPart ( filename , {
274- type : "file" ,
275- mime : "text/plain" ,
276- filename,
277- url,
278- source : {
279- type : "file" ,
280- text : {
281- start : 0 ,
282- end : 0 ,
283- value : "" ,
284- } ,
285- path : item ,
286- } ,
287- } )
288- } ,
289- }
290- } ) ,
291- )
292- }
332+ } ) ,
333+ )
334+ }
293335
294336 return options
295337 } ,
@@ -501,6 +543,14 @@ export function Autocomplete(props: {
501543 }
502544
503545 onMount ( ( ) => {
546+ const unsubscribeMention = editor . onMention ( ( mention ) => {
547+ insertFileMention ( mention )
548+ } )
549+
550+ onCleanup ( ( ) => {
551+ unsubscribeMention ( )
552+ } )
553+
504554 props . ref ( {
505555 get visible ( ) {
506556 return store . visible
0 commit comments