diff --git a/src/common/localize.ts b/src/common/localize.ts index fcda9e52..ab39e50e 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -138,3 +138,8 @@ export namespace ProjectCreatorString { export const noProjectsFound = l10n.t('No projects found'); } + +export namespace EnvViewStrings { + export const selectedGlobalTooltip = l10n.t('This environment is selected for non-workspace files'); + export const selectedWorkspaceTooltip = l10n.t('This environment is selected for workspace files'); +} diff --git a/src/common/utils/pythonPath.ts b/src/common/utils/pythonPath.ts index 451da4b1..bdc469ef 100644 --- a/src/common/utils/pythonPath.ts +++ b/src/common/utils/pythonPath.ts @@ -2,7 +2,7 @@ import { Uri, Progress, CancellationToken } from 'vscode'; import { PythonEnvironment } from '../../api'; import { InternalEnvironmentManager } from '../../internal.api'; import { showErrorMessage } from '../errors/utils'; -import { traceInfo, traceVerbose, traceError } from '../logging'; +import { traceVerbose, traceError } from '../logging'; import { PYTHON_EXTENSION_ID } from '../constants'; const priorityOrder = [ @@ -47,10 +47,10 @@ export async function handlePythonPath( return; } reporter?.report({ message: `Checking ${manager.displayName}` }); - traceInfo(`Checking ${manager.displayName} (${manager.id}) for ${interpreterUri.fsPath}`); + traceVerbose(`Checking ${manager.displayName} (${manager.id}) for ${interpreterUri.fsPath}`); const env = await manager.resolve(interpreterUri); if (env) { - traceInfo(`Using ${manager.displayName} (${manager.id}) to handle ${interpreterUri.fsPath}`); + traceVerbose(`Using ${manager.displayName} (${manager.id}) to handle ${interpreterUri.fsPath}`); return env; } traceVerbose(`Manager ${manager.displayName} (${manager.id}) cannot handle ${interpreterUri.fsPath}`); @@ -66,10 +66,10 @@ export async function handlePythonPath( return; } reporter?.report({ message: `Checking ${manager.displayName}` }); - traceInfo(`Checking ${manager.displayName} (${manager.id}) for ${interpreterUri.fsPath}`); + traceVerbose(`Checking ${manager.displayName} (${manager.id}) for ${interpreterUri.fsPath}`); const env = await manager.resolve(interpreterUri); if (env) { - traceInfo(`Using ${manager.displayName} (${manager.id}) to handle ${interpreterUri.fsPath}`); + traceVerbose(`Using ${manager.displayName} (${manager.id}) to handle ${interpreterUri.fsPath}`); return env; } } diff --git a/src/common/window.apis.ts b/src/common/window.apis.ts index 55f06c1f..ed9611af 100644 --- a/src/common/window.apis.ts +++ b/src/common/window.apis.ts @@ -3,6 +3,7 @@ import { CancellationToken, Disposable, ExtensionTerminalOptions, + FileDecorationProvider, InputBox, InputBoxOptions, LogOutputChannel, @@ -290,3 +291,7 @@ export function createOutputChannel(name: string, languageId?: string): OutputCh export function createLogOutputChannel(name: string): LogOutputChannel { return window.createOutputChannel(name, { log: true }); } + +export function registerFileDecorationProvider(provider: FileDecorationProvider): Disposable { + return window.registerFileDecorationProvider(provider); +} diff --git a/src/extension.ts b/src/extension.ts index 142da287..b6880eed 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -212,6 +212,7 @@ export async function activate(context: ExtensionContext): Promise { + managerView.environmentChanged(e); const location = e.uri?.fsPath ?? 'global'; traceInfo( `Internal: Changed environment from ${e.old?.displayName} to ${e.new?.displayName} for: ${location}`, diff --git a/src/features/views/envManagersView.ts b/src/features/views/envManagersView.ts index 2c04ade5..5a131fcb 100644 --- a/src/features/views/envManagersView.ts +++ b/src/features/views/envManagersView.ts @@ -1,5 +1,5 @@ import { Disposable, Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeView, window } from 'vscode'; -import { EnvironmentGroupInfo, PythonEnvironment } from '../../api'; +import { DidChangeEnvironmentEventArgs, EnvironmentGroupInfo, PythonEnvironment } from '../../api'; import { DidChangeEnvironmentManagerEventArgs, DidChangePackageManagerEventArgs, @@ -32,6 +32,7 @@ export class EnvManagerView implements TreeDataProvider, Disposable private revealMap = new Map(); private managerViews = new Map(); private packageRoots = new Map(); + private selected: Map = new Map(); private disposables: Disposable[] = []; public constructor(public providers: EnvironmentManagers) { @@ -44,6 +45,7 @@ export class EnvManagerView implements TreeDataProvider, Disposable this.packageRoots.clear(); this.revealMap.clear(); this.managerViews.clear(); + this.selected.clear(); }), this.treeView, this.treeDataChanged, @@ -99,7 +101,7 @@ export class EnvManagerView implements TreeDataProvider, Disposable const views: EnvTreeItem[] = []; const envs = await manager.getEnvironments('all'); envs.filter((e) => !e.group).forEach((env) => { - const view = new PythonEnvTreeItem(env, element as EnvManagerTreeItem); + const view = new PythonEnvTreeItem(env, element as EnvManagerTreeItem, this.selected.get(env.envId.id)); views.push(view); this.revealMap.set(env.envId.id, view); }); @@ -142,7 +144,7 @@ export class EnvManagerView implements TreeDataProvider, Disposable }); grouped.forEach((env) => { - const view = new PythonEnvTreeItem(env, groupItem); + const view = new PythonEnvTreeItem(env, groupItem, this.selected.get(env.envId.id)); views.push(view); this.revealMap.set(env.envId.id, view); }); @@ -227,4 +229,29 @@ export class EnvManagerView implements TreeDataProvider, Disposable const roots = Array.from(this.packageRoots.values()).filter((r) => r.manager.id === args.manager.id); this.fireDataChanged(roots); } + + public environmentChanged(e: DidChangeEnvironmentEventArgs) { + const views = []; + if (e.old) { + this.selected.delete(e.old.envId.id); + let view: EnvTreeItem | undefined = this.packageRoots.get(e.old.envId.id); + if (!view) { + view = this.managerViews.get(e.old.envId.managerId); + } + if (view) { + views.push(view); + } + } + if (e.new) { + this.selected.set(e.new.envId.id, e.uri === undefined ? 'global' : e.uri.fsPath); + let view: EnvTreeItem | undefined = this.packageRoots.get(e.new.envId.id); + if (!view) { + view = this.managerViews.get(e.new.envId.managerId); + } + if (view && !views.includes(view)) { + views.push(view); + } + } + this.fireDataChanged(views); + } } diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 7197c87d..842ef8c9 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -3,6 +3,7 @@ import { InternalEnvironmentManager, InternalPackageManager } from '../../intern import { PythonEnvironment, IconPath, Package, PythonProject, EnvironmentGroupInfo } from '../../api'; import { removable } from './utils'; import { isActivatableEnvironment } from '../common/activation'; +import { EnvViewStrings } from '../../common/localize'; export enum EnvTreeItemKind { manager = 'python-env-manager', @@ -66,11 +67,21 @@ export class PythonEnvTreeItem implements EnvTreeItem { constructor( public readonly environment: PythonEnvironment, public readonly parent: EnvManagerTreeItem | PythonGroupEnvTreeItem, + public readonly selected?: string, ) { - const item = new TreeItem(environment.displayName ?? environment.name, TreeItemCollapsibleState.Collapsed); + let name = environment.displayName ?? environment.name; + let tooltip = environment.tooltip; + if (selected) { + const tooltipEnd = environment.tooltip ?? environment.description; + tooltip = + selected === 'global' ? EnvViewStrings.selectedGlobalTooltip : EnvViewStrings.selectedWorkspaceTooltip; + tooltip = tooltipEnd ? `${tooltip} ● ${tooltipEnd}` : tooltip; + } + + const item = new TreeItem(name, TreeItemCollapsibleState.Collapsed); item.contextValue = this.getContextValue(); item.description = environment.description; - item.tooltip = environment.tooltip; + item.tooltip = tooltip; item.iconPath = environment.iconPath; this.treeItem = item; } diff --git a/src/internal.api.ts b/src/internal.api.ts index 59165bd0..fb38d49d 100644 --- a/src/internal.api.ts +++ b/src/internal.api.ts @@ -71,8 +71,26 @@ export interface EnvironmentManagers extends Disposable { registerEnvironmentManager(manager: EnvironmentManager): Disposable; registerPackageManager(manager: PackageManager): Disposable; + /** + * This event is fired when any environment manager changes its collection of environments. + * This can be any environment manager even if it is not the one selected by the user for the workspace. + */ onDidChangeEnvironments: Event; + + /** + * This event is fired when an environment manager changes the environment for + * a particular scope (global, uri, workspace, etc). This can be any environment manager even if it is not the + * one selected by the user for the workspace. It is also fired if the change + * involves unselected to selected or selected to unselected. + */ onDidChangeEnvironment: Event; + + /** + * This event is fired when a selected environment manager changes the environment + * for a particular scope (global, uri, workspace, etc). This is also only fired if + * the previous and current environments are different. It is also fired if the change + * involves unselected to selected or selected to unselected. + */ onDidChangeEnvironmentFiltered: Event; onDidChangePackages: Event;