Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/client/chat/installPackagesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { resolveFilePath } from './utils';
import { IModuleInstaller } from '../common/installer/types';
import { ModuleInstallerType } from '../pythonEnvironments/info';
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
import { trackEnvUsedByTool } from './lastUsedEnvs';

export interface IInstallPackageArgs {
resourcePath?: string;
Expand Down Expand Up @@ -67,7 +66,6 @@ export class InstallPackagesTool implements LanguageModelTool<IInstallPackageArg
for (const packageName of options.input.packageList) {
await installer.installModule(packageName, resourcePath, token, undefined, { installAsProcess: true });
}
trackEnvUsedByTool(resourcePath, environment);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old code no longer required

// format and return
const resultMessage = `Successfully installed ${packagePlurality}: ${options.input.packageList.join(', ')}`;
return new LanguageModelToolResult([new LanguageModelTextPart(resultMessage)]);
Expand Down
81 changes: 0 additions & 81 deletions src/client/chat/lastUsedEnvs.ts

This file was deleted.

2 changes: 0 additions & 2 deletions src/client/chat/listPackagesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { parsePipList } from './pipListUtils';
import { Conda } from '../pythonEnvironments/common/environmentManagers/conda';
import { traceError } from '../logging';
import { IDiscoveryAPI } from '../pythonEnvironments/base/locator';
import { trackEnvUsedByTool } from './lastUsedEnvs';

export interface IResourceReference {
resourcePath?: string;
Expand Down Expand Up @@ -109,7 +108,6 @@ export async function getPythonPackagesResponse(
if (!packages.length) {
return 'No packages found';
}
trackEnvUsedByTool(resourcePath, environment);
// Installed Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown. Returns an empty array if no packages are installed.
const response = [
'Below is a list of the Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown: ',
Expand Down
2 changes: 0 additions & 2 deletions src/client/chat/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { PythonExtension, ResolvedEnvironment } from '../api/types';
import { ITerminalHelper, TerminalShellType } from '../common/terminal/types';
import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution';
import { Conda } from '../pythonEnvironments/common/environmentManagers/conda';
import { trackEnvUsedByTool } from './lastUsedEnvs';

export function resolveFilePath(filepath?: string): Uri | undefined {
if (!filepath) {
Expand Down Expand Up @@ -71,7 +70,6 @@ export async function getEnvironmentDetails(
getTerminalCommand(environment, resourcePath, terminalExecutionService, terminalHelper),
token,
);
trackEnvUsedByTool(resourcePath, environment);
const message = [
`Following is the information about the Python environment:`,
`1. Environment Type: ${environment.environment?.type || 'unknown'}`,
Expand Down
23 changes: 23 additions & 0 deletions src/client/common/utils/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,26 @@ export async function waitForCondition(
}, 10);
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isPromiseLike<T>(v: any): v is PromiseLike<T> {
return typeof v?.then === 'function';
}

export function raceTimeout<T>(timeout: number, ...promises: Promise<T>[]): Promise<T | undefined>;
export function raceTimeout<T>(timeout: number, defaultValue: T, ...promises: Promise<T>[]): Promise<T>;
export function raceTimeout<T>(timeout: number, defaultValue: T, ...promises: Promise<T>[]): Promise<T> {
const resolveValue = isPromiseLike(defaultValue) ? undefined : defaultValue;
if (isPromiseLike(defaultValue)) {
promises.push((defaultValue as unknown) as Promise<T>);
}

let promiseResolve: ((value: T) => void) | undefined = undefined;

const timer = setTimeout(() => promiseResolve?.((resolveValue as unknown) as T), timeout);

return Promise.race([
Promise.race(promises).finally(() => clearTimeout(timer)),
new Promise<T>((resolve) => (promiseResolve = resolve)),
]);
}
5 changes: 5 additions & 0 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { ProposedExtensionAPI } from './proposedApiTypes';
import { buildProposedApi } from './proposedApi';
import { GLOBAL_PERSISTENT_KEYS } from './common/persistentState';
import { registerTools } from './chat';
import { RecommendedEnvironmentService } from './interpreter/configuration/recommededEnvironmentService';
import { IRecommendedEnvironmentService } from './interpreter/configuration/types';

durations.codeLoadingTime = stopWatch.elapsedTime;

Expand Down Expand Up @@ -164,6 +166,9 @@ async function activateUnsafe(
);
const proposedApi = buildProposedApi(components.pythonEnvs, ext.legacyIOC.serviceContainer);
registerTools(context, components.pythonEnvs, api.environments, ext.legacyIOC.serviceContainer);
ext.legacyIOC.serviceContainer
.get<IRecommendedEnvironmentService>(RecommendedEnvironmentService)
.registerEnvApi(api.environments);
return [{ ...api, ...proposedApi }, activationPromise, ext.legacyIOC.serviceContainer];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,11 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
return Promise.resolve();
}

/**
* @returns true when an interpreter was set, undefined if the user cancelled the quickpick.
*/
@captureTelemetry(EventName.SELECT_INTERPRETER)
public async setInterpreter(): Promise<void> {
public async setInterpreter(): Promise<true | undefined> {
const targetConfig = await this.getConfigTargets();
if (!targetConfig) {
return;
Expand All @@ -588,6 +591,7 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
if (useEnvExtension()) {
await setInterpreterLegacy(interpreterState.path, wkspace);
}
return true;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { PythonInterpreterTelemetry } from '../../telemetry/types';
import { IComponentAdapter } from '../contracts';
import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager } from './types';
import {
IRecommendedEnvironmentService,
IPythonPathUpdaterServiceFactory,
IPythonPathUpdaterServiceManager,
} from './types';

@injectable()
export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManager {
constructor(
@inject(IPythonPathUpdaterServiceFactory)
private readonly pythonPathSettingsUpdaterFactory: IPythonPathUpdaterServiceFactory,
@inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter,
@inject(IRecommendedEnvironmentService) private readonly preferredEnvService: IRecommendedEnvironmentService,
) {}

public async updatePythonPath(
Expand All @@ -28,6 +33,9 @@ export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManage
let failed = false;
try {
await pythonPathUpdater.updatePythonPath(pythonPath);
if (trigger === 'ui') {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is the right spot for tracking what env is selected by the user.

this.preferredEnvService.trackUserSelectedEnvironment(pythonPath, wkspace);
}
} catch (err) {
failed = true;
const reason = err as Error;
Expand Down
102 changes: 102 additions & 0 deletions src/client/interpreter/configuration/recommededEnvironmentService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { inject, injectable } from 'inversify';
import { IRecommendedEnvironmentService } from './types';
import { PythonExtension } from '../../api/types';
import { IExtensionContext, Resource } from '../../common/types';
import { Uri, workspace } from 'vscode';
import { getWorkspaceStateValue, updateWorkspaceStateValue } from '../../common/persistentState';
import { traceError } from '../../logging';

const MEMENTO_KEY = 'userSelectedEnvPath';

@injectable()
export class RecommendedEnvironmentService implements IRecommendedEnvironmentService {
private api?: PythonExtension['environments'];
constructor(@inject(IExtensionContext) private readonly extensionContext: IExtensionContext) {}

registerEnvApi(api: PythonExtension['environments']) {
this.api = api;
}

trackUserSelectedEnvironment(environmentPath: string | undefined, uri: Uri | undefined) {
if (workspace.workspaceFolders?.length) {
try {
void updateWorkspaceStateValue(MEMENTO_KEY, getDataToStore(environmentPath, uri));
} catch (ex) {
traceError('Failed to update workspace state for preferred environment', ex);
}
} else {
void this.extensionContext.globalState.update(MEMENTO_KEY, environmentPath);
}
}

getRecommededEnvironment(
resource: Resource,
):
| { environmentPath: string; reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended' }
| undefined {
let workspaceState: string | undefined = undefined;
try {
workspaceState = getWorkspaceStateValue<string>(MEMENTO_KEY);
} catch (ex) {
traceError('Failed to get workspace state for preferred environment', ex);
}

if (workspace.workspaceFolders?.length && workspaceState) {
const workspaceUri = (
(resource ? workspace.getWorkspaceFolder(resource)?.uri : undefined) ||
workspace.workspaceFolders[0].uri
).toString();

try {
const existingJson: Record<string, string> = JSON.parse(workspaceState);
const selectedEnvPath = existingJson[workspaceUri];
if (selectedEnvPath) {
return { environmentPath: selectedEnvPath, reason: 'workspaceUserSelected' };
}
} catch (ex) {
traceError('Failed to parse existing workspace state value for preferred environment', ex);
}
}

const globalSelectedEnvPath = this.extensionContext.globalState.get<string | undefined>(MEMENTO_KEY);
if (globalSelectedEnvPath) {
return { environmentPath: globalSelectedEnvPath, reason: 'globalUserSelected' };
}
return this.api && workspace.isTrusted
? {
environmentPath: this.api.getActiveEnvironmentPath(resource).path,
reason: 'defaultRecommended',
}
: undefined;
}
}

function getDataToStore(environmentPath: string | undefined, uri: Uri | undefined): string | undefined {
if (!workspace.workspaceFolders?.length) {
return environmentPath;
}
const workspaceUri = (
(uri ? workspace.getWorkspaceFolder(uri)?.uri : undefined) || workspace.workspaceFolders[0].uri
).toString();
const existingData = getWorkspaceStateValue<string>(MEMENTO_KEY);
if (!existingData) {
return JSON.stringify(environmentPath ? { [workspaceUri]: environmentPath } : {});
}
try {
const existingJson: Record<string, string> = JSON.parse(existingData);
if (environmentPath) {
existingJson[workspaceUri] = environmentPath;
} else {
delete existingJson[workspaceUri];
}
return JSON.stringify(existingJson);
} catch (ex) {
traceError('Failed to parse existing workspace state value for preferred environment', ex);
return JSON.stringify({
[workspaceUri]: environmentPath,
});
}
}
12 changes: 12 additions & 0 deletions src/client/interpreter/configuration/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConfigurationTarget, Disposable, QuickPickItem, Uri } from 'vscode';
import { Resource } from '../../common/types';
import { PythonEnvironment } from '../../pythonEnvironments/info';
import { PythonExtension } from '../../api/types';

export interface IPythonPathUpdaterService {
updatePythonPath(pythonPath: string | undefined): Promise<void>;
Expand Down Expand Up @@ -96,3 +97,14 @@ export interface IInterpreterQuickPick {
params?: InterpreterQuickPickParams,
): Promise<string | undefined>;
}

export const IRecommendedEnvironmentService = Symbol('IRecommendedEnvironmentService');
export interface IRecommendedEnvironmentService {
registerEnvApi(api: PythonExtension['environments']): void;
trackUserSelectedEnvironment(environmentPath: string | undefined, uri: Uri | undefined): void;
getRecommededEnvironment(
resource: Resource,
):
| { environmentPath: string; reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended' }
| undefined;
}
6 changes: 6 additions & 0 deletions src/client/interpreter/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import { InstallPythonViaTerminal } from './configuration/interpreterSelector/co
import { ResetInterpreterCommand } from './configuration/interpreterSelector/commands/resetInterpreter';
import { SetInterpreterCommand } from './configuration/interpreterSelector/commands/setInterpreter';
import { InterpreterSelector } from './configuration/interpreterSelector/interpreterSelector';
import { RecommendedEnvironmentService } from './configuration/recommededEnvironmentService';
import { PythonPathUpdaterService } from './configuration/pythonPathUpdaterService';
import { PythonPathUpdaterServiceFactory } from './configuration/pythonPathUpdaterServiceFactory';
import {
IInterpreterComparer,
IInterpreterQuickPick,
IInterpreterSelector,
IRecommendedEnvironmentService,
IPythonPathUpdaterServiceFactory,
IPythonPathUpdaterServiceManager,
} from './configuration/types';
Expand Down Expand Up @@ -59,6 +61,10 @@ export function registerInterpreterTypes(serviceManager: IServiceManager): void
IExtensionSingleActivationService,
ResetInterpreterCommand,
);
serviceManager.addSingleton<IRecommendedEnvironmentService>(
IRecommendedEnvironmentService,
RecommendedEnvironmentService,
);
serviceManager.addSingleton(IInterpreterQuickPick, SetInterpreterCommand);

serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, VirtualEnvironmentPrompt);
Expand Down
Loading
Loading