forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Added environments reducer #13953
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Added environments reducer #13953
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
4cf7c16
Add environments reducer
6f9c4d9
Added tests
e6a9260
Use path.join to construct paths
f2b5f80
Code reviews
1f00f38
Correct dummy implementations and adjust tests
3b24031
Modify resolveEnv()
5ec529a
Rename to a general parentLocator
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 165 additions & 0 deletions
165
src/client/pythonEnvironments/collection/environmentsReducer.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { cloneDeep, isEqual } from 'lodash'; | ||
import { Event, EventEmitter } from 'vscode'; | ||
import { traceVerbose } from '../../common/logger'; | ||
import { createDeferred } from '../../common/utils/async'; | ||
import { areSameEnvironment, PythonEnvInfo, PythonEnvKind } from '../base/info'; | ||
import { | ||
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent, | ||
} from '../base/locator'; | ||
import { PythonEnvsChangedEvent } from '../base/watcher'; | ||
|
||
/** | ||
* Combines duplicate environments received from the incoming locator into one and passes on unique environments | ||
*/ | ||
export class PythonEnvsReducer implements ILocator { | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public get onChanged(): Event<PythonEnvsChangedEvent> { | ||
return this.pythonEnvsManager.onChanged; | ||
} | ||
|
||
constructor(private readonly pythonEnvsManager: ILocator) {} | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> { | ||
let environment: PythonEnvInfo | undefined; | ||
const waitForUpdatesDeferred = createDeferred<void>(); | ||
const iterator = this.iterEnvs(); | ||
iterator.onUpdated!((event) => { | ||
if (event === null) { | ||
waitForUpdatesDeferred.resolve(); | ||
} else if (environment && areSameEnvironment(environment, event.new)) { | ||
environment = event.new; | ||
} | ||
}); | ||
let result = await iterator.next(); | ||
while (!result.done) { | ||
if (areSameEnvironment(result.value, env)) { | ||
environment = result.value; | ||
} | ||
// eslint-disable-next-line no-await-in-loop | ||
result = await iterator.next(); | ||
} | ||
if (!environment) { | ||
return undefined; | ||
} | ||
await waitForUpdatesDeferred.promise; | ||
return this.pythonEnvsManager.resolveEnv(environment); | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public iterEnvs(query?: QueryForEvent<PythonEnvsChangedEvent>): IPythonEnvsIterator { | ||
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent | null>(); | ||
const incomingIterator = this.pythonEnvsManager.iterEnvs(query); | ||
const iterator: IPythonEnvsIterator = iterEnvsIterator(incomingIterator, didUpdate); | ||
iterator.onUpdated = didUpdate.event; | ||
return iterator; | ||
} | ||
} | ||
|
||
async function* iterEnvsIterator( | ||
iterator: IPythonEnvsIterator, | ||
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>, | ||
): AsyncIterator<PythonEnvInfo, void> { | ||
const state = { | ||
done: false, | ||
pending: 0, | ||
}; | ||
const seen: PythonEnvInfo[] = []; | ||
|
||
if (iterator.onUpdated !== undefined) { | ||
iterator.onUpdated((event) => { | ||
if (event === null) { | ||
state.done = true; | ||
checkIfFinishedAndNotify(state, didUpdate); | ||
} else { | ||
const oldIndex = seen.findIndex((s) => areSameEnvironment(s, event.old)); | ||
if (oldIndex !== -1) { | ||
state.pending += 1; | ||
resolveDifferencesInBackground(oldIndex, event.new, state, didUpdate, seen).ignoreErrors(); | ||
} else { | ||
// This implies a problem in a downstream locator | ||
traceVerbose(`Expected already iterated env, got ${event.old}`); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
let result = await iterator.next(); | ||
while (!result.done) { | ||
const currEnv = result.value; | ||
const oldIndex = seen.findIndex((s) => areSameEnvironment(s, currEnv)); | ||
if (oldIndex !== -1) { | ||
state.pending += 1; | ||
resolveDifferencesInBackground(oldIndex, currEnv, state, didUpdate, seen).ignoreErrors(); | ||
} else { | ||
// We haven't yielded a matching env so yield this one as-is. | ||
yield currEnv; | ||
seen.push(currEnv); | ||
} | ||
// eslint-disable-next-line no-await-in-loop | ||
result = await iterator.next(); | ||
} | ||
if (iterator.onUpdated === undefined) { | ||
state.done = true; | ||
checkIfFinishedAndNotify(state, didUpdate); | ||
} | ||
} | ||
|
||
async function resolveDifferencesInBackground( | ||
oldIndex: number, | ||
newEnv: PythonEnvInfo, | ||
state: { done: boolean; pending: number }, | ||
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>, | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
seen: PythonEnvInfo[], | ||
) { | ||
const oldEnv = seen[oldIndex]; | ||
const merged = mergeEnvironments(oldEnv, newEnv); | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!isEqual(oldEnv, merged)) { | ||
didUpdate.fire({ old: oldEnv, new: merged }); | ||
seen[oldIndex] = merged; | ||
} | ||
state.pending -= 1; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
checkIfFinishedAndNotify(state, didUpdate); | ||
} | ||
|
||
/** | ||
* When all info from incoming iterator has been received and all background calls finishes, notify that we're done | ||
* @param state Carries the current state of progress | ||
* @param didUpdate Used to notify when finished | ||
*/ | ||
function checkIfFinishedAndNotify( | ||
state: { done: boolean; pending: number }, | ||
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>, | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) { | ||
if (state.done && state.pending === 0) { | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
didUpdate.fire(null); | ||
didUpdate.dispose(); | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
export function mergeEnvironments(environment: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo { | ||
ericsnowcurrently marked this conversation as resolved.
Show resolved
Hide resolved
ericsnowcurrently marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const result = cloneDeep(environment); | ||
// Preserve type information. | ||
// Possible we identified environment as unknown, but a later provider has identified env type. | ||
if (environment.kind === PythonEnvKind.Unknown && other.kind && other.kind !== PythonEnvKind.Unknown) { | ||
result.kind = other.kind; | ||
} | ||
const props: (keyof PythonEnvInfo)[] = [ | ||
'version', | ||
'kind', | ||
'executable', | ||
'name', | ||
'arch', | ||
'distro', | ||
'defaultDisplayName', | ||
'searchLocation', | ||
]; | ||
props.forEach((prop) => { | ||
if (!result[prop] && other[prop]) { | ||
// tslint:disable: no-any | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(result as any)[prop] = other[prop]; | ||
} | ||
}); | ||
return result; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.