(
projectPath: string,
query: string,
limit?: number
)
| 1219 | } |
| 1220 | |
| 1221 | async getFileCompletions( |
| 1222 | projectPath: string, |
| 1223 | query: string, |
| 1224 | limit?: number |
| 1225 | ): Promise<{ paths: string[] }> { |
| 1226 | const resolvedLimit = limit ?? 20; |
| 1227 | |
| 1228 | if (typeof projectPath !== "string" || projectPath.trim().length === 0) { |
| 1229 | return { paths: [] }; |
| 1230 | } |
| 1231 | |
| 1232 | const validation = await validateProjectPath(projectPath); |
| 1233 | if (!validation.valid) { |
| 1234 | return { paths: [] }; |
| 1235 | } |
| 1236 | |
| 1237 | const normalizedPath = validation.expandedPath!; |
| 1238 | |
| 1239 | let cacheEntry = this.fileCompletionsCache.get(normalizedPath); |
| 1240 | if (!cacheEntry) { |
| 1241 | cacheEntry = { index: EMPTY_FILE_COMPLETIONS_INDEX, fetchedAt: 0 }; |
| 1242 | this.fileCompletionsCache.set(normalizedPath, cacheEntry); |
| 1243 | } |
| 1244 | |
| 1245 | const now = Date.now(); |
| 1246 | const isStale = |
| 1247 | cacheEntry.fetchedAt === 0 || now - cacheEntry.fetchedAt > FILE_COMPLETIONS_CACHE_TTL_MS; |
| 1248 | |
| 1249 | if (isStale && !cacheEntry.refreshing) { |
| 1250 | cacheEntry.refreshing = (async () => { |
| 1251 | try { |
| 1252 | // Sub-projects share a parent git repository; match branch listing by |
| 1253 | // accepting any path inside a work tree rather than requiring `.git` |
| 1254 | // directly under the project directory. |
| 1255 | if (!(await isInsideGitRepository(normalizedPath))) { |
| 1256 | cacheEntry.index = EMPTY_FILE_COMPLETIONS_INDEX; |
| 1257 | return; |
| 1258 | } |
| 1259 | |
| 1260 | using proc = execFileAsync("git", [ |
| 1261 | "-C", |
| 1262 | normalizedPath, |
| 1263 | "ls-files", |
| 1264 | "-co", |
| 1265 | "--exclude-standard", |
| 1266 | ]); |
| 1267 | const { stdout } = await proc.result; |
| 1268 | |
| 1269 | const files = stdout |
| 1270 | .split("\n") |
| 1271 | .map((line) => line.trim()) |
| 1272 | .filter((line) => line.length > 0) |
| 1273 | // File @mentions are whitespace-delimited (extractAtMentions uses /@(\\S+)/), so |
| 1274 | // suggestions containing spaces would be inserted incorrectly (e.g. "@foo bar.ts"). |
| 1275 | .filter((filePath) => !/\s/.test(filePath)); |
| 1276 | |
| 1277 | cacheEntry.index = buildFileCompletionsIndex(files); |
| 1278 | } catch (error) { |
no test coverage detected