()
| 624 | let currentIDESearch: AbortController | null = null |
| 625 | |
| 626 | export async function findAvailableIDE(): Promise<DetectedIDEInfo | null> { |
| 627 | if (currentIDESearch) { |
| 628 | currentIDESearch.abort() |
| 629 | } |
| 630 | currentIDESearch = createAbortController() |
| 631 | const signal = currentIDESearch.signal |
| 632 | |
| 633 | // Clean up stale IDE lockfiles first so we don't check them at all. |
| 634 | await cleanupStaleIdeLockfiles() |
| 635 | const startTime = Date.now() |
| 636 | while (Date.now() - startTime < 30_000 && !signal.aborted) { |
| 637 | // Skip iteration during scroll drain — detectIDEs reads lockfiles + |
| 638 | // shells out to ps, competing for the event loop with scroll frames. |
| 639 | // Next tick after scroll settles resumes the search. |
| 640 | if (getIsScrollDraining()) { |
| 641 | await sleep(1000, signal) |
| 642 | continue |
| 643 | } |
| 644 | const ides = await detectIDEs(false) |
| 645 | if (signal.aborted) { |
| 646 | return null |
| 647 | } |
| 648 | // Return the IDE if and only if there is exactly one match, otherwise the user must |
| 649 | // use /ide to select an IDE. When running from a supported built-in terminal, detectIDEs() |
| 650 | // should return at most one IDE. |
| 651 | if (ides.length === 1) { |
| 652 | return ides[0]! |
| 653 | } |
| 654 | await sleep(1000, signal) |
| 655 | } |
| 656 | return null |
| 657 | } |
| 658 | |
| 659 | /** |
| 660 | * Detects IDEs that have a running extension/plugin. |
no test coverage detected