export interface FileMentionRange { start: number; end: number; query: string; } interface FindActiveFileMentionInput { text: string; cursorIndex: number; } interface ApplyFileMentionReplacementInput { text: string; mention: FileMentionRange; relativePath: string; } const INVALID_MENTION_QUERY_CHARS = /[\s\n\r\t"']/; export function findActiveFileMention(input: FindActiveFileMentionInput): FileMentionRange | null { const clampedCursor = Math.max(0, Math.min(input.cursorIndex, input.text.length)); const beforeCursor = input.text.slice(0, clampedCursor); for ( let atIndex = beforeCursor.lastIndexOf("@"); atIndex >= 0; atIndex = beforeCursor.lastIndexOf("@", atIndex - 1) ) { const query = beforeCursor.slice(atIndex + 1); if (INVALID_MENTION_QUERY_CHARS.test(query)) { continue; } return { start: atIndex, end: clampedCursor, query, }; } return null; } export function applyFileMentionReplacement(input: ApplyFileMentionReplacementInput): string { const safePath = input.relativePath.replace(/"/g, '\\"'); const before = input.text.slice(0, input.mention.start); const after = input.text.slice(input.mention.end); return `${before}"${safePath}"${after}`; }