From edfb0bef312210cdd10c68d08f775a49321106b4 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 21 Jan 2025 16:16:58 -0800 Subject: [PATCH 1/5] Add a decorator on the selected environment --- src/common/localize.ts | 4 ++ src/common/window.apis.ts | 5 +++ src/extension.ts | 2 + src/features/views/selectedDecorator.ts | 56 +++++++++++++++++++++++++ src/features/views/treeViewItems.ts | 2 + 5 files changed, 69 insertions(+) create mode 100644 src/features/views/selectedDecorator.ts diff --git a/src/common/localize.ts b/src/common/localize.ts index fcda9e52..e62274fc 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -138,3 +138,7 @@ export namespace ProjectCreatorString { export const noProjectsFound = l10n.t('No projects found'); } + +export namespace SelectedDecoratorStrings { + export const selectedToolTip = l10n.t('This environment is selected'); +} 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..bee62a90 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -55,6 +55,7 @@ import { EventNames } from './common/telemetry/constants'; import { ensureCorrectVersion } from './common/extVersion'; import { ExistingProjects } from './features/creators/existingProjects'; import { AutoFindProjects } from './features/creators/autoFindProjects'; +import { registerSelectedDecorator } from './features/views/selectedDecorator'; export async function activate(context: ExtensionContext): Promise { const start = new StopWatch(); @@ -103,6 +104,7 @@ export async function activate(context: ExtensionContext): Promise outputChannel.show()), commands.registerCommand('python-envs.refreshManager', async (item) => { await refreshManagerCommand(item); diff --git a/src/features/views/selectedDecorator.ts b/src/features/views/selectedDecorator.ts new file mode 100644 index 00000000..a1e8f6aa --- /dev/null +++ b/src/features/views/selectedDecorator.ts @@ -0,0 +1,56 @@ +import { + CancellationToken, + Disposable, + Event, + EventEmitter, + FileDecoration, + FileDecorationProvider, + ThemeColor, + Uri, +} from 'vscode'; +import { registerFileDecorationProvider } from '../../common/window.apis'; +import { SelectedDecoratorStrings } from '../../common/localize'; +import { PythonProjectEnvironmentApi } from '../../api'; + +class SelectedEnvDecorationProvider implements FileDecorationProvider, Disposable { + private readonly onDidChangeFileDecorationsEmitter = new EventEmitter(); + private readonly disposables: Disposable[] = []; + private readonly selected: Set = new Set(); + constructor(private em: PythonProjectEnvironmentApi) { + this.disposables.push( + this.em.onDidChangeEnvironment((e) => { + const uris = []; + if (e.old) { + this.selected.delete(e.old.environmentPath.toString()); + uris.push(e.old.environmentPath); + } + if (e.new) { + this.selected.add(e.new.environmentPath.toString()); + uris.push(e.new.environmentPath); + } + this.onDidChangeFileDecorationsEmitter.fire(uris); + }), + this.onDidChangeFileDecorationsEmitter, + ); + } + + onDidChangeFileDecorations?: Event | undefined = + this.onDidChangeFileDecorationsEmitter.event; + + async provideFileDecoration(uri: Uri, _token: CancellationToken): Promise { + if (!this.selected.has(uri.toString())) { + return undefined; + } + return { + badge: 'S', + color: new ThemeColor('testing.iconPassed'), + tooltip: SelectedDecoratorStrings.selectedToolTip, + }; + } + + dispose() {} +} + +export function registerSelectedDecorator(em: PythonProjectEnvironmentApi): Disposable { + return registerFileDecorationProvider(new SelectedEnvDecorationProvider(em)); +} diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 7197c87d..938c6730 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -72,6 +72,7 @@ export class PythonEnvTreeItem implements EnvTreeItem { item.description = environment.description; item.tooltip = environment.tooltip; item.iconPath = environment.iconPath; + item.resourceUri = environment.environmentPath; this.treeItem = item; } @@ -261,6 +262,7 @@ export class ProjectEnvironment implements ProjectTreeItem { item.description = this.environment.description; item.tooltip = this.environment.tooltip; item.iconPath = this.environment.iconPath; + item.resourceUri = this.environment.environmentPath; this.treeItem = item; } From 60a9dcc17de9e8f8c9c8f65d364ac96b77ddcd4f Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 22 Jan 2025 11:32:53 -0800 Subject: [PATCH 2/5] Clean up --- src/common/localize.ts | 4 -- src/extension.ts | 2 - src/features/views/selectedDecorator.ts | 56 ------------------------- src/features/views/treeViewItems.ts | 2 - 4 files changed, 64 deletions(-) delete mode 100644 src/features/views/selectedDecorator.ts diff --git a/src/common/localize.ts b/src/common/localize.ts index e62274fc..fcda9e52 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -138,7 +138,3 @@ export namespace ProjectCreatorString { export const noProjectsFound = l10n.t('No projects found'); } - -export namespace SelectedDecoratorStrings { - export const selectedToolTip = l10n.t('This environment is selected'); -} diff --git a/src/extension.ts b/src/extension.ts index bee62a90..142da287 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -55,7 +55,6 @@ import { EventNames } from './common/telemetry/constants'; import { ensureCorrectVersion } from './common/extVersion'; import { ExistingProjects } from './features/creators/existingProjects'; import { AutoFindProjects } from './features/creators/autoFindProjects'; -import { registerSelectedDecorator } from './features/views/selectedDecorator'; export async function activate(context: ExtensionContext): Promise { const start = new StopWatch(); @@ -104,7 +103,6 @@ export async function activate(context: ExtensionContext): Promise outputChannel.show()), commands.registerCommand('python-envs.refreshManager', async (item) => { await refreshManagerCommand(item); diff --git a/src/features/views/selectedDecorator.ts b/src/features/views/selectedDecorator.ts deleted file mode 100644 index a1e8f6aa..00000000 --- a/src/features/views/selectedDecorator.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - CancellationToken, - Disposable, - Event, - EventEmitter, - FileDecoration, - FileDecorationProvider, - ThemeColor, - Uri, -} from 'vscode'; -import { registerFileDecorationProvider } from '../../common/window.apis'; -import { SelectedDecoratorStrings } from '../../common/localize'; -import { PythonProjectEnvironmentApi } from '../../api'; - -class SelectedEnvDecorationProvider implements FileDecorationProvider, Disposable { - private readonly onDidChangeFileDecorationsEmitter = new EventEmitter(); - private readonly disposables: Disposable[] = []; - private readonly selected: Set = new Set(); - constructor(private em: PythonProjectEnvironmentApi) { - this.disposables.push( - this.em.onDidChangeEnvironment((e) => { - const uris = []; - if (e.old) { - this.selected.delete(e.old.environmentPath.toString()); - uris.push(e.old.environmentPath); - } - if (e.new) { - this.selected.add(e.new.environmentPath.toString()); - uris.push(e.new.environmentPath); - } - this.onDidChangeFileDecorationsEmitter.fire(uris); - }), - this.onDidChangeFileDecorationsEmitter, - ); - } - - onDidChangeFileDecorations?: Event | undefined = - this.onDidChangeFileDecorationsEmitter.event; - - async provideFileDecoration(uri: Uri, _token: CancellationToken): Promise { - if (!this.selected.has(uri.toString())) { - return undefined; - } - return { - badge: 'S', - color: new ThemeColor('testing.iconPassed'), - tooltip: SelectedDecoratorStrings.selectedToolTip, - }; - } - - dispose() {} -} - -export function registerSelectedDecorator(em: PythonProjectEnvironmentApi): Disposable { - return registerFileDecorationProvider(new SelectedEnvDecorationProvider(em)); -} diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 938c6730..7197c87d 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -72,7 +72,6 @@ export class PythonEnvTreeItem implements EnvTreeItem { item.description = environment.description; item.tooltip = environment.tooltip; item.iconPath = environment.iconPath; - item.resourceUri = environment.environmentPath; this.treeItem = item; } @@ -262,7 +261,6 @@ export class ProjectEnvironment implements ProjectTreeItem { item.description = this.environment.description; item.tooltip = this.environment.tooltip; item.iconPath = this.environment.iconPath; - item.resourceUri = this.environment.environmentPath; this.treeItem = item; } From c22aa4ee56f819cc3062c6f1ee6a585a5a239f1d Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 22 Jan 2025 19:17:14 -0800 Subject: [PATCH 3/5] USe environment changed event --- src/common/localize.ts | 5 +++++ src/features/views/envManagersView.ts | 30 +++++++++++++++++++++++++-- src/features/views/treeViewItems.ts | 18 ++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) 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/features/views/envManagersView.ts b/src/features/views/envManagersView.ts index 2c04ade5..519b417d 100644 --- a/src/features/views/envManagersView.ts +++ b/src/features/views/envManagersView.ts @@ -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, @@ -60,6 +62,30 @@ export class EnvManagerView implements TreeDataProvider, Disposable this.providers.onDidChangePackageManager((p: DidChangePackageManagerEventArgs) => { this.onDidChangePackageManager(p); }), + this.providers.onDidChangeEnvironment((e) => { + 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); + }), ); } @@ -99,7 +125,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 +168,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); }); diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 7197c87d..616791a7 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,24 @@ 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; + if (selected) { + name = `★ ${name}`; + } + + let tooltip = environment.tooltip; + if (selected) { + tooltip = + selected === 'global' ? EnvViewStrings.selectedGlobalTooltip : EnvViewStrings.selectedWorkspaceTooltip; + tooltip = `${tooltip} ● ${environment.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; } From f84a23ca15ba924dad2f79562773bc78eaf51369 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 23 Jan 2025 08:35:22 -0800 Subject: [PATCH 4/5] =?UTF-8?q?Use=20=E2=98=85=20and=20tooltip=20message?= =?UTF-8?q?=20to=20indicate=20selected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/utils/pythonPath.ts | 10 +++--- src/extension.ts | 1 + src/features/views/envManagersView.ts | 51 ++++++++++++++------------- src/features/views/treeViewItems.ts | 3 +- src/internal.api.ts | 18 ++++++++++ 5 files changed, 52 insertions(+), 31 deletions(-) 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/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 519b417d..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, @@ -62,30 +62,6 @@ export class EnvManagerView implements TreeDataProvider, Disposable this.providers.onDidChangePackageManager((p: DidChangePackageManagerEventArgs) => { this.onDidChangePackageManager(p); }), - this.providers.onDidChangeEnvironment((e) => { - 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); - }), ); } @@ -253,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 616791a7..0d9aba53 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -76,9 +76,10 @@ export class PythonEnvTreeItem implements EnvTreeItem { let tooltip = environment.tooltip; if (selected) { + const tooltipEnd = environment.tooltip ?? environment.description; tooltip = selected === 'global' ? EnvViewStrings.selectedGlobalTooltip : EnvViewStrings.selectedWorkspaceTooltip; - tooltip = `${tooltip} ● ${environment.tooltip}`; + tooltip = tooltipEnd ? `${tooltip} ● ${tooltipEnd}` : tooltip; } const item = new TreeItem(name, TreeItemCollapsibleState.Collapsed); 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; From 04b3cafc9a4f20160e736c01faa3054467d9987a Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 23 Jan 2025 11:09:42 -0800 Subject: [PATCH 5/5] =?UTF-8?q?Remove=20=E2=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/views/treeViewItems.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/features/views/treeViewItems.ts b/src/features/views/treeViewItems.ts index 0d9aba53..842ef8c9 100644 --- a/src/features/views/treeViewItems.ts +++ b/src/features/views/treeViewItems.ts @@ -70,10 +70,6 @@ export class PythonEnvTreeItem implements EnvTreeItem { public readonly selected?: string, ) { let name = environment.displayName ?? environment.name; - if (selected) { - name = `★ ${name}`; - } - let tooltip = environment.tooltip; if (selected) { const tooltipEnd = environment.tooltip ?? environment.description;