Skip to content

Commit 072cf93

Browse files
authored
Resolve system variables in defaultInterpreterPath entry (#18215)
* Resolve sys vars in defaultInterpreterPath entry * Add news entry
1 parent 3147ddc commit 072cf93

File tree

3 files changed

+92
-6
lines changed

3 files changed

+92
-6
lines changed

news/2 Fixes/18207.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Resolve system variables in `python.defaultInterpreterPath`.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
InputStep,
2121
IQuickPickParameters,
2222
} from '../../../../common/utils/multiStepInput';
23+
import { SystemVariables } from '../../../../common/variables/systemVariables';
2324
import { REFRESH_BUTTON_ICON } from '../../../../debugger/extension/attachQuickPick/types';
2425
import { captureTelemetry, sendTelemetryEvent } from '../../../../telemetry';
2526
import { EventName } from '../../../../telemetry/constants';
@@ -162,7 +163,8 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
162163

163164
private getDefaultInterpreterPathSuggestion(resource: Resource): ISpecialQuickPickItem | undefined {
164165
const config = this.workspaceService.getConfiguration('python', resource);
165-
const defaultInterpreterPathValue = config.get<string>('defaultInterpreterPath');
166+
const systemVariables = new SystemVariables(resource, undefined, this.workspaceService);
167+
const defaultInterpreterPathValue = systemVariables.resolveAny(config.get<string>('defaultInterpreterPath'));
166168
if (defaultInterpreterPathValue && defaultInterpreterPathValue !== 'python') {
167169
return {
168170
label: `${Octicons.Gear} ${InterpreterQuickPickList.defaultInterpreterPath.label()}`,

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

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { expect } from 'chai';
66
import * as path from 'path';
77
import * as sinon from 'sinon';
88
import * as TypeMoq from 'typemoq';
9-
import { ConfigurationTarget, OpenDialogOptions, QuickPickItem, Uri } from 'vscode';
9+
import { ConfigurationTarget, OpenDialogOptions, QuickPick, QuickPickItem, Uri } from 'vscode';
1010
import { cloneDeep } from 'lodash';
1111
import { instance, mock, verify, when } from 'ts-mockito';
1212
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../client/common/application/types';
@@ -36,6 +36,7 @@ import { MockWorkspaceConfiguration } from '../../../mocks/mockWorkspaceConfig';
3636
import { Octicons } from '../../../../client/common/constants';
3737
import { IInterpreterService, PythonEnvironmentsChangedEvent } from '../../../../client/interpreter/contracts';
3838
import { createDeferred, sleep } from '../../../../client/common/utils/async';
39+
import { SystemVariables } from '../../../../client/common/variables/systemVariables';
3940

4041
const untildify = require('untildify');
4142

@@ -219,6 +220,90 @@ suite('Set Interpreter Command', () => {
219220
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
220221
});
221222

223+
test('If system variables are used in the default interpreter path, make sure they are resolved when the path is displayed', async () => {
224+
// Create a SetInterpreterCommand instance from scratch, and use a different defaultInterpreterPath from the rest of the tests.
225+
const workspaceDefaultInterpreterPath = '${workspaceFolder}/defaultInterpreterPath';
226+
227+
const systemVariables = new SystemVariables(undefined, undefined, workspace.object);
228+
const pathUtils = new PathUtils(false);
229+
230+
const expandedPath = systemVariables.resolveAny(workspaceDefaultInterpreterPath);
231+
const expandedDetail = pathUtils.getDisplayName(expandedPath);
232+
233+
pythonSettings = TypeMoq.Mock.ofType<IPythonSettings>();
234+
workspace = TypeMoq.Mock.ofType<IWorkspaceService>();
235+
236+
pythonSettings.setup((p) => p.pythonPath).returns(() => currentPythonPath);
237+
pythonSettings.setup((p) => p.defaultInterpreterPath).returns(() => workspaceDefaultInterpreterPath);
238+
configurationService.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);
239+
workspace.setup((w) => w.rootPath).returns(() => 'rootPath');
240+
workspace
241+
.setup((w) => w.getConfiguration(TypeMoq.It.isValue('python'), TypeMoq.It.isAny()))
242+
.returns(
243+
() =>
244+
new MockWorkspaceConfiguration({
245+
defaultInterpreterPath: workspaceDefaultInterpreterPath,
246+
}),
247+
);
248+
249+
setInterpreterCommand = new SetInterpreterCommand(
250+
appShell.object,
251+
pathUtils,
252+
pythonPathUpdater.object,
253+
configurationService.object,
254+
commandManager.object,
255+
multiStepInputFactory.object,
256+
platformService.object,
257+
interpreterSelector.object,
258+
workspace.object,
259+
instance(interpreterService),
260+
);
261+
262+
// Test info
263+
const state: InterpreterStateArgs = { path: 'some path', workspace: undefined };
264+
const multiStepInput = TypeMoq.Mock.ofType<IMultiStepInput<InterpreterStateArgs>>();
265+
const recommended = cloneDeep(item);
266+
recommended.label = `${Octicons.Star} ${item.label}`;
267+
recommended.description = Common.recommended();
268+
269+
const defaultPathSuggestion = {
270+
label: `${Octicons.Gear} ${InterpreterQuickPickList.defaultInterpreterPath.label()}`,
271+
detail: expandedDetail,
272+
path: expandedPath,
273+
alwaysShow: true,
274+
};
275+
276+
const suggestions = [expectedEnterInterpreterPathSuggestion, defaultPathSuggestion, recommended];
277+
const expectedParameters: IQuickPickParameters<QuickPickItem> = {
278+
placeholder: InterpreterQuickPickList.quickPickListPlaceholder().format(currentPythonPath),
279+
items: suggestions,
280+
activeItem: recommended,
281+
matchOnDetail: true,
282+
matchOnDescription: true,
283+
title: InterpreterQuickPickList.browsePath.openButtonLabel(),
284+
sortByLabel: true,
285+
keepScrollPosition: true,
286+
};
287+
let actualParameters: IQuickPickParameters<QuickPickItem> | undefined;
288+
multiStepInput
289+
.setup((i) => i.showQuickPick(TypeMoq.It.isAny()))
290+
.callback((options) => {
291+
actualParameters = options;
292+
})
293+
.returns(() => Promise.resolve((undefined as unknown) as QuickPickItem));
294+
295+
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state);
296+
297+
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
298+
const refreshButtonCallback = actualParameters!.customButtonSetup?.callback;
299+
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
300+
301+
delete actualParameters!.customButtonSetup;
302+
delete actualParameters!.onChangeItem;
303+
304+
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
305+
});
306+
222307
test('Ensure a refresh is triggered if refresh button is clicked', async () => {
223308
const state: InterpreterStateArgs = { path: 'some path', workspace: undefined };
224309
const multiStepInput = TypeMoq.Mock.ofType<IMultiStepInput<InterpreterStateArgs>>();
@@ -237,8 +322,7 @@ suite('Set Interpreter Command', () => {
237322
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
238323

239324
when(interpreterService.triggerRefresh()).thenResolve();
240-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
241-
await refreshButtonCallback!({} as any); // Invoke callback, meaning that the refresh button is clicked.
325+
await refreshButtonCallback!({} as QuickPick<QuickPickItem>); // Invoke callback, meaning that the refresh button is clicked.
242326
verify(interpreterService.triggerRefresh()).once();
243327
});
244328

@@ -276,8 +360,7 @@ suite('Set Interpreter Command', () => {
276360
old: item.interpreter,
277361
new: refreshedItem.interpreter,
278362
};
279-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
280-
await onChangedCallback!(changeEvent, quickPick as any); // Invoke callback, meaning that the items are supposed to change.
363+
await onChangedCallback!(changeEvent, (quickPick as unknown) as QuickPick<QuickPickItem>); // Invoke callback, meaning that the items are supposed to change.
281364

282365
assert.deepStrictEqual(
283366
quickPick,

0 commit comments

Comments
 (0)