From 14d5ca09e3cb30bf6c504952873252c42dc23c01 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 16 Sep 2020 10:49:59 -0600 Subject: [PATCH 1/3] Return IPythonEnvsIterator from iterEnvs() rather than PythonEnvsIterator. --- src/client/pythonEnvironments/base/locator.ts | 10 ++++++---- src/client/pythonEnvironments/base/locators.ts | 6 +++--- .../pythonEnvironments/discovery/locators/index.ts | 4 ++-- src/client/pythonEnvironments/index.ts | 4 ++-- src/test/pythonEnvironments/base/common.ts | 6 +++--- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 618f4b1fc0eb..bd87e763bbdc 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -9,12 +9,14 @@ import { BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, PythonEnvsChangedEvent /** * An async iterator of `PythonEnvInfo`. */ -export type PythonEnvsIterator = AsyncIterator; +export interface IPythonEnvsIterator extends AsyncIterator { + // extra info goes here +} /** * An empty Python envs iterator. */ -export const NOOP_ITERATOR: PythonEnvsIterator = iterEmpty(); +export const NOOP_ITERATOR: IPythonEnvsIterator = iterEmpty(); /** * The most basic info to send to a locator when requesting environments. @@ -68,7 +70,7 @@ export interface ILocator): PythonEnvsIterator; + iterEnvs(query?: QueryForEvent): IPythonEnvsIterator; /** * Find the given Python environment and fill in as much missing info as possible. @@ -111,7 +113,7 @@ export abstract class LocatorBase): PythonEnvsIterator; + public abstract iterEnvs(query?: QueryForEvent): IPythonEnvsIterator; public async resolveEnv(_env: string | PythonEnvInfo): Promise { return undefined; diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index 50169bfa590f..c43b9a86c1b3 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -3,7 +3,7 @@ import { chain } from '../../common/utils/async'; import { PythonEnvInfo } from './info'; -import { ILocator, NOOP_ITERATOR, PythonEnvsIterator, PythonLocatorQuery } from './locator'; +import { ILocator, IPythonEnvsIterator, NOOP_ITERATOR, PythonLocatorQuery } from './locator'; import { DisableableEnvsWatcher, PythonEnvsWatchers } from './watchers'; /** @@ -19,7 +19,7 @@ export class Locators extends PythonEnvsWatchers implements ILocator { super(locators); } - public iterEnvs(query?: PythonLocatorQuery): PythonEnvsIterator { + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators = this.locators.map((loc) => loc.iterEnvs(query)); return chain(iterators); } @@ -50,7 +50,7 @@ export class DisableableLocator extends DisableableEnvsWatcher implements ILocat super(locator); } - public iterEnvs(query?: PythonLocatorQuery): PythonEnvsIterator { + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { if (!this.enabled) { return NOOP_ITERATOR; } diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts index 422ba916d78e..33c04f88050e 100644 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ b/src/client/pythonEnvironments/discovery/locators/index.ts @@ -21,7 +21,7 @@ import { } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; import { PythonEnvInfo } from '../../base/info'; -import { ILocator, Locator, NOOP_ITERATOR, PythonEnvsIterator, PythonLocatorQuery } from '../../base/locator'; +import { ILocator, IPythonEnvsIterator, Locator, NOOP_ITERATOR, PythonLocatorQuery } from '../../base/locator'; import { DisableableLocator, Locators } from '../../base/locators'; import { PythonEnvironment } from '../../info'; import { isHiddenInterpreter } from './services/interpreterFilter'; @@ -83,7 +83,7 @@ export class WorkspaceLocators extends Locator { folders.onRemoved((root: Uri) => this.removeRoot(root)); } - public iterEnvs(query?: PythonLocatorQuery): PythonEnvsIterator { + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators = Object.keys(this.locators).map((key) => { if (query?.searchLocations) { const root = this.roots[key]; diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 9b2ae4ff18a9..d737eb255562 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import { IServiceContainer, IServiceManager } from '../ioc/types'; import { PythonEnvInfo } from './base/info'; -import { ILocator, PythonEnvsIterator, PythonLocatorQuery } from './base/locator'; +import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; import { ExtensionLocators, WorkspaceLocators } from './discovery/locators'; import { registerForIOC } from './legacyIOC'; @@ -33,7 +33,7 @@ export class PythonEnvironments implements ILocator { return this.locators.onChanged; } - public iterEnvs(query?: PythonLocatorQuery): PythonEnvsIterator { + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { return this.locators.iterEnvs(query); } diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index 626445bf4e12..e55e0a64391b 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -10,7 +10,7 @@ import { PythonReleaseLevel, PythonVersion } from '../../../client/pythonEnvironments/base/info'; -import { Locator, PythonEnvsIterator, PythonLocatorQuery } from '../../../client/pythonEnvironments/base/locator'; +import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../../client/pythonEnvironments/base/locator'; import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher'; export function createEnv( @@ -111,7 +111,7 @@ export class SimpleLocator extends Locator { public fire(event: PythonEnvsChangedEvent) { this.emitter.fire(event); } - public iterEnvs(query?: PythonLocatorQuery): PythonEnvsIterator { + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const deferred = this.deferred; const callbacks = this.callbacks; let envs = this.envs; @@ -161,6 +161,6 @@ export class SimpleLocator extends Locator { } } -export async function getEnvs(iterator: PythonEnvsIterator): Promise { +export async function getEnvs(iterator: IPythonEnvsIterator): Promise { return flattenIterator(iterator); } From 93e2e041c3dfbf12851217ff758574e593ee1d23 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 15 Sep 2020 10:58:50 -0600 Subject: [PATCH 2/3] Add an "onUpdated" event to IPythonEnvsIterator. --- src/client/pythonEnvironments/base/locator.ts | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index bd87e763bbdc..03eb206445bd 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -4,13 +4,62 @@ import { Event, Uri } from 'vscode'; import { iterEmpty } from '../../common/utils/async'; import { PythonEnvInfo, PythonEnvKind } from './info'; -import { BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, PythonEnvsChangedEvent, PythonEnvsWatcher } from './watcher'; +import { + BasicPythonEnvsChangedEvent, + IPythonEnvsWatcher, + PythonEnvsChangedEvent, + PythonEnvsWatcher +} from './watcher'; /** - * An async iterator of `PythonEnvInfo`. + * A single update to a previously provided Python env object. + */ +export type PythonEnvUpdatedEvent = { + /** + * The env info that was previously provided. + * + * If the event comes from `IPythonEnvsIterator.onUpdated` then + * `old` was previously yielded during iteration. + */ + old: PythonEnvInfo; + /** + * The env info that replaces the old info. + */ + new: PythonEnvInfo; +}; + +/** + * A fast async iterator of Python envs, which may have incomplete info. + * + * Each object yielded by the iterator represents a unique Python + * environment. + * + * The iterator is not required to have provide all info about + * an environment. However, each yielded item will at least + * include all the `PythonEnvBaseInfo` data. + * + * During iteration the information for an already + * yielded object may be updated. Rather than updating the yielded + * object or yielding it again with updated info, the update is + * emitted by the iterator's `onUpdated` (event) property. Once there are no more updates, the event emits + * `null`. + * + * If the iterator does not have `onUpdated` then it means the + * provider does not support updates. + * + * Callers can usually ignore the update event entirely and rely on + * the locator to provide sufficiently complete information. */ export interface IPythonEnvsIterator extends AsyncIterator { - // extra info goes here + /** + * Provides possible updates for already-iterated envs. + * + * Once there are no more updates, `null` is emitted. + * + * If this property is not provided then it means the iterator does + * not support updates. + */ + onUpdated?: Event; } /** @@ -66,9 +115,16 @@ export interface ILocator): IPythonEnvsIterator; From 8b4fe0ef67920de5862a19c3cb2e7895b011d18a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 16 Sep 2020 12:11:24 -0600 Subject: [PATCH 3/3] Combine update events for locators that combine iterators. --- .../pythonEnvironments/base/locators.ts | 41 ++++++++++++++++++- .../discovery/locators/index.ts | 18 ++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index c43b9a86c1b3..15a67d83b734 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -1,11 +1,48 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { EventEmitter } from 'vscode'; import { chain } from '../../common/utils/async'; import { PythonEnvInfo } from './info'; -import { ILocator, IPythonEnvsIterator, NOOP_ITERATOR, PythonLocatorQuery } from './locator'; +import { + ILocator, + IPythonEnvsIterator, + NOOP_ITERATOR, + PythonEnvUpdatedEvent, + PythonLocatorQuery +} from './locator'; import { DisableableEnvsWatcher, PythonEnvsWatchers } from './watchers'; +/** + * Combine the `onUpdated` event of the given iterators into a single event. + */ +export function combineIterators(iterators: IPythonEnvsIterator[]): IPythonEnvsIterator { + const result: IPythonEnvsIterator = chain(iterators); + const events = iterators.map((it) => it.onUpdated).filter((v) => v); + if (!events || events.length === 0) { + // There are no sub-events, so we leave `onUpdated` undefined. + return result; + } + + const emitter = new EventEmitter(); + let numActive = events.length; + events.forEach((event) => { + event!((e: PythonEnvUpdatedEvent | null) => { + if (e === null) { + numActive -= 1; + if (numActive === 0) { + // All the sub-events are done so we're done. + emitter.fire(null); + } + } else { + emitter.fire(e); + } + }); + }); + result.onUpdated = emitter.event; + return result; +} + /** * A wrapper around a set of locators, exposing them as a single locator. * @@ -21,7 +58,7 @@ export class Locators extends PythonEnvsWatchers implements ILocator { public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators = this.locators.map((loc) => loc.iterEnvs(query)); - return chain(iterators); + return combineIterators(iterators); } public async resolveEnv(env: string | PythonEnvInfo): Promise { diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts index 33c04f88050e..4ebf360f765a 100644 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ b/src/client/pythonEnvironments/discovery/locators/index.ts @@ -5,7 +5,7 @@ import { import { traceDecorators } from '../../../common/logger'; import { IPlatformService } from '../../../common/platform/types'; import { IDisposableRegistry } from '../../../common/types'; -import { chain, createDeferred, Deferred } from '../../../common/utils/async'; +import { createDeferred, Deferred } from '../../../common/utils/async'; import { OSType } from '../../../common/utils/platform'; import { CONDA_ENV_FILE_SERVICE, @@ -21,8 +21,18 @@ import { } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; import { PythonEnvInfo } from '../../base/info'; -import { ILocator, IPythonEnvsIterator, Locator, NOOP_ITERATOR, PythonLocatorQuery } from '../../base/locator'; -import { DisableableLocator, Locators } from '../../base/locators'; +import { + ILocator, + IPythonEnvsIterator, + Locator, + NOOP_ITERATOR, + PythonLocatorQuery, +} from '../../base/locator'; +import { + combineIterators, + DisableableLocator, + Locators, +} from '../../base/locators'; import { PythonEnvironment } from '../../info'; import { isHiddenInterpreter } from './services/interpreterFilter'; import { GetInterpreterLocatorOptions } from './types'; @@ -95,7 +105,7 @@ export class WorkspaceLocators extends Locator { const locator = this.locators[key]; return locator.iterEnvs(query); }); - return chain(iterators); + return combineIterators(iterators); } public async resolveEnv(env: string | PythonEnvInfo): Promise {