Skip to content

Commit 8233730

Browse files
author
Kartik Raj
committed
Ensure full refresh icon removes any environment which isn't relevant and remove hard refresh icon in quickpick (microsoft#19806)
1 parent 0a67d79 commit 8233730

File tree

11 files changed

+78
-275
lines changed

11 files changed

+78
-275
lines changed

src/client/apiTypes.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,6 @@ export interface ActiveEnvironmentChangedParams {
121121
resource?: Uri;
122122
}
123123

124-
export interface RefreshEnvironmentsOptions {
125-
clearCache?: boolean;
126-
}
127-
128124
export interface IProposedExtensionAPI {
129125
environment: {
130126
/**
@@ -203,7 +199,7 @@ export interface IProposedExtensionAPI {
203199
* * clearCache : When true, this will clear the cache before environment refresh
204200
* is triggered.
205201
*/
206-
refreshEnvironment(options?: RefreshEnvironmentsOptions): Promise<EnvPathType[] | undefined>;
202+
refreshEnvironment(): Promise<EnvPathType[] | undefined>;
207203
/**
208204
* Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant
209205
* stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of

src/client/common/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ export namespace Octicons {
8585
* to change the icons.
8686
*/
8787
export namespace ThemeIcons {
88-
export const ClearAll = 'clear-all';
8988
export const Refresh = 'refresh';
9089
export const SpinningLoader = 'loading~spin';
9190
}

src/client/common/utils/localize.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,6 @@ export namespace InterpreterQuickPickList {
317317
'InterpreterQuickPickList.refreshInterpreterList',
318318
'Refresh Interpreter list',
319319
);
320-
export const clearAllAndRefreshInterpreterList = localize(
321-
'InterpreterQuickPickList.clearAllAndRefreshInterpreterList',
322-
'Clear all and Refresh Interpreter list',
323-
);
324320
export const refreshingInterpreterList = localize(
325321
'InterpreterQuickPickList.refreshingInterpreterList',
326322
'Refreshing Interpreter list...',

src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
8080
tooltip: InterpreterQuickPickList.refreshInterpreterList,
8181
};
8282

83-
private readonly hardRefreshButton = {
84-
iconPath: new ThemeIcon(ThemeIcons.ClearAll),
85-
tooltip: InterpreterQuickPickList.clearAllAndRefreshInterpreterList,
86-
};
87-
8883
private readonly noPythonInstalled: ISpecialQuickPickItem = {
8984
label: `${Octicons.Error} ${InterpreterQuickPickList.noPythonInstalled}`,
9085
detail: InterpreterQuickPickList.clickForInstructions,
@@ -155,16 +150,10 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
155150
matchOnDescription: true,
156151
title: InterpreterQuickPickList.browsePath.openButtonLabel,
157152
customButtonSetups: [
158-
{
159-
button: this.hardRefreshButton,
160-
callback: (quickpickInput) => {
161-
this.refreshButtonCallback(quickpickInput, true);
162-
},
163-
},
164153
{
165154
button: this.refreshButton,
166155
callback: (quickpickInput) => {
167-
this.refreshButtonCallback(quickpickInput, false);
156+
this.refreshButtonCallback(quickpickInput);
168157
},
169158
},
170159
],
@@ -418,17 +407,17 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
418407
}
419408
}
420409

421-
private refreshButtonCallback(input: QuickPick<QuickPickItem>, clearCache: boolean) {
410+
private refreshButtonCallback(input: QuickPick<QuickPickItem>) {
422411
input.buttons = [
423412
{
424413
iconPath: new ThemeIcon(ThemeIcons.SpinningLoader),
425414
tooltip: InterpreterQuickPickList.refreshingInterpreterList,
426415
},
427416
];
428417
this.interpreterService
429-
.triggerRefresh(undefined, { clearCache })
418+
.triggerRefresh()
430419
.finally(() => {
431-
input.buttons = [this.hardRefreshButton, this.refreshButton];
420+
input.buttons = [this.refreshButton];
432421
})
433422
.ignoreErrors();
434423
}

src/client/interpreter/interpreterService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export class InterpreterService implements Disposable, IInterpreterService {
244244
traceLog('Conda envs without Python are known to not work well; fixing conda environment...');
245245
const promise = installer.install(Product.python, await this.getInterpreterDetails(pythonPath));
246246
shell.withProgress(progressOptions, () => promise);
247-
promise.then(() => this.triggerRefresh(undefined, { clearCache: true }).ignoreErrors());
247+
promise.then(() => this.triggerRefresh().ignoreErrors());
248248
}
249249
}
250250
}

src/client/proposedApi.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
EnvironmentDetailsOptions,
99
EnvironmentsChangedParams,
1010
IProposedExtensionAPI,
11-
RefreshEnvironmentsOptions,
1211
} from './apiTypes';
1312
import { arePathsSame } from './common/platform/fs-paths';
1413
import { IInterpreterPathService, Resource } from './common/types';
@@ -101,8 +100,8 @@ export function buildProposedApi(
101100
setActiveEnvironment(path: string, resource?: Resource): Promise<void> {
102101
return interpreterPathService.update(resource, ConfigurationTarget.WorkspaceFolder, path);
103102
},
104-
async refreshEnvironment(options?: RefreshEnvironmentsOptions) {
105-
await discoveryApi.triggerRefresh(undefined, options ? { clearCache: options.clearCache } : undefined);
103+
async refreshEnvironment() {
104+
await discoveryApi.triggerRefresh();
106105
const paths = discoveryApi.getEnvs().map((e) => getEnvPath(e.executable.filename, e.location));
107106
return Promise.resolve(paths);
108107
},

src/client/pythonEnvironments/base/locator.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,6 @@ export interface GetRefreshEnvironmentsOptions {
194194
}
195195

196196
export type TriggerRefreshOptions = {
197-
/**
198-
* Trigger a fresh refresh.
199-
*/
200-
clearCache?: boolean;
201197
/**
202198
* Only trigger a refresh if it hasn't already been triggered for this session.
203199
*/

src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { Event } from 'vscode';
5+
import { isTestExecution } from '../../../../common/constants';
56
import { traceInfo } from '../../../../logging';
67
import { reportInterpretersChanged } from '../../../../proposedApi';
78
import { arePathsSame, getFileInfo, pathExists } from '../../../common/externalDependencies';
@@ -52,13 +53,9 @@ export interface IEnvsCollectionCache {
5253
/**
5354
* Removes invalid envs from cache. Note this does not check for outdated info when
5455
* validating cache.
56+
* @param latestListOfEnvs Carries list of latest envs for this workspace session if known.
5557
*/
56-
validateCache(): Promise<void>;
57-
58-
/**
59-
* Clears the in-memory cache. This can be used for hard refresh.
60-
*/
61-
clearCache(): Promise<void>;
58+
validateCache(latestListOfEnvs?: PythonEnvInfo[]): Promise<void>;
6259
}
6360

6461
export type PythonEnvLatestInfo = { hasLatestInfo?: boolean } & PythonEnvInfo;
@@ -79,15 +76,35 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher<PythonEnvCollectionCha
7976
super();
8077
}
8178

82-
public async validateCache(): Promise<void> {
79+
public async validateCache(latestListOfEnvs?: PythonEnvInfo[]): Promise<void> {
8380
/**
8481
* We do check if an env has updated as we already run discovery in background
8582
* which means env cache will have up-to-date envs eventually. This also means
86-
* we avoid the cost of running lstat. So simply remove envs which no longer
87-
* exist.
83+
* we avoid the cost of running lstat. So simply remove envs which are no longer
84+
* valid.
8885
*/
8986
const areEnvsValid = await Promise.all(
90-
this.envs.map((e) => (e.executable.filename === 'python' ? true : pathExists(e.executable.filename))),
87+
this.envs.map(async (cachedEnv) => {
88+
const { path } = getEnvPath(cachedEnv.executable.filename, cachedEnv.location);
89+
if (await pathExists(path)) {
90+
if (latestListOfEnvs) {
91+
/**
92+
* Only consider a cached env to be valid if it's relevant. That means:
93+
* * It is either reported in the latest complete refresh for this session.
94+
* * Or it is relevant for some other workspace folder which is not opened currently.
95+
*/
96+
if (cachedEnv.searchLocation) {
97+
return true;
98+
}
99+
if (latestListOfEnvs.some((env) => cachedEnv.id === env.id)) {
100+
return true;
101+
}
102+
} else {
103+
return true;
104+
}
105+
}
106+
return false;
107+
}),
91108
);
92109
const invalidIndexes = areEnvsValid
93110
.map((isValid, index) => (isValid ? -1 : index))
@@ -194,6 +211,15 @@ async function validateInfo(env: PythonEnvInfo) {
194211
export async function createCollectionCache(storage: IPersistentStorage): Promise<PythonEnvInfoCache> {
195212
const cache = new PythonEnvInfoCache(storage);
196213
await cache.clearAndReloadFromStorage();
197-
await cache.validateCache();
214+
await validateCache(cache);
198215
return cache;
199216
}
217+
218+
async function validateCache(cache: PythonEnvInfoCache) {
219+
if (isTestExecution()) {
220+
// For purposes for test execution, block on validation so that we can determinally know when it finishes.
221+
return cache.validateCache();
222+
}
223+
// Validate in background so it doesn't block on returning the API object.
224+
return cache.validateCache().ignoreErrors();
225+
}

src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,12 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
108108
// Do not trigger another refresh if a refresh has previously finished.
109109
return Promise.resolve();
110110
}
111-
refreshPromise = this.startRefresh(query, options);
111+
refreshPromise = this.startRefresh(query);
112112
}
113113
return refreshPromise.then(() => this.sendTelemetry(query, stopWatch));
114114
}
115115

116-
private startRefresh(query: PythonLocatorQuery | undefined, options?: TriggerRefreshOptions): Promise<void> {
117-
if (options?.clearCache) {
118-
this.cache.clearCache();
119-
}
116+
private startRefresh(query: PythonLocatorQuery | undefined): Promise<void> {
120117
this.createProgressStates(query);
121118
const promise = this.addEnvsToCacheForQuery(query);
122119
return promise
@@ -176,7 +173,8 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
176173
this.cache.addEnv(env);
177174
}
178175
await updatesDone.promise;
179-
await this.cache.validateCache();
176+
// If query for all envs is done, `seen` should contain the list of all envs.
177+
await this.cache.validateCache(query === undefined ? seen : undefined);
180178
this.cache.flush().ignoreErrors();
181179
}
182180

src/test/configuration/interpreterSelector/commands/setInterpreter.unit.test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
WorkspaceFolder,
1717
} from 'vscode';
1818
import { cloneDeep } from 'lodash';
19-
import { anything, instance, mock, when } from 'ts-mockito';
19+
import { anything, instance, mock, when, verify } from 'ts-mockito';
2020
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../client/common/application/types';
2121
import { PathUtils } from '../../../../client/common/platform/pathUtils';
2222
import { IPlatformService } from '../../../../client/common/platform/types';
@@ -80,6 +80,7 @@ suite('Set Interpreter Command', () => {
8080
workspace = TypeMoq.Mock.ofType<IWorkspaceService>();
8181
interpreterService = mock<IInterpreterService>();
8282
when(interpreterService.refreshPromise).thenReturn(undefined);
83+
when(interpreterService.triggerRefresh()).thenResolve();
8384
when(interpreterService.triggerRefresh(anything(), anything())).thenResolve();
8485
workspace.setup((w) => w.rootPath).returns(() => 'rootPath');
8586

@@ -574,17 +575,11 @@ suite('Set Interpreter Command', () => {
574575
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
575576
const refreshButtons = actualParameters!.customButtonSetups;
576577
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
578+
expect(refreshButtons?.length).to.equal(1);
577579

578-
expect(refreshButtons?.length).to.equal(2);
579-
let arg;
580-
when(interpreterService.triggerRefresh(undefined, anything())).thenCall((_, _arg) => {
581-
arg = _arg;
582-
return Promise.resolve();
583-
});
584580
await refreshButtons![0].callback!({} as QuickPick<QuickPickItem>); // Invoke callback, meaning that the refresh button is clicked.
585-
expect(arg).to.deep.equal({ clearCache: true });
586-
await refreshButtons![1].callback!({} as QuickPick<QuickPickItem>); // Invoke callback, meaning that the refresh button is clicked.
587-
expect(arg).to.deep.equal({ clearCache: false });
581+
582+
verify(interpreterService.triggerRefresh()).once();
588583
});
589584

590585
test('Events to update quickpick updates the quickpick accordingly', async () => {

0 commit comments

Comments
 (0)