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