()
| 651 | let currentIDESearch: AbortController | null = null |
| 652 | |
| 653 | export async function findAvailableIDE(): Promise<DetectedIDEInfo | null> { |
| 654 | if (currentIDESearch) { |
| 655 | currentIDESearch.abort() |
| 656 | } |
| 657 | currentIDESearch = createAbortController() |
| 658 | const signal = currentIDESearch.signal |
| 659 | |
| 660 | // Clean up stale IDE lockfiles first so we don't check them at all. |
| 661 | await cleanupStaleIdeLockfiles() |
| 662 | const startTime = Date.now() |
| 663 | while (Date.now() - startTime < 30_000 && !signal.aborted) { |
| 664 | // Skip iteration during scroll drain — detectIDEs reads lockfiles + |
| 665 | // shells out to ps, competing for the event loop with scroll frames. |
| 666 | // Next tick after scroll settles resumes the search. |
| 667 | if (getIsScrollDraining()) { |
| 668 | await sleep(1000, signal) |
| 669 | continue |
| 670 | } |
| 671 | const ides = await detectIDEs(false) |
| 672 | if (signal.aborted) { |
| 673 | return null |
| 674 | } |
| 675 | // Return the IDE if and only if there is exactly one match, otherwise the user must |
| 676 | // use /ide to select an IDE. When running from a supported built-in terminal, detectIDEs() |
| 677 | // should return at most one IDE. |
| 678 | if (ides.length === 1) { |
| 679 | return ides[0]! |
| 680 | } |
| 681 | await sleep(1000, signal) |
| 682 | } |
| 683 | return null |
| 684 | } |
| 685 | |
| 686 | /** |
| 687 | * Detects IDEs that have a running extension/plugin. |
no test coverage detected