Skip to content

Commit 60124ab

Browse files
Implement temporary state management for environment actions (#1009)
Closes #461 Introduce a temporary state manager to provide feedback for actions like copying paths and selecting environments. --------- Co-authored-by: Eleanor Boyd <26030610+eleanorjboyd@users.noreply.github.com>
1 parent a1d812e commit 60124ab

File tree

7 files changed

+372
-16
lines changed

7 files changed

+372
-16
lines changed

package.json

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@
193193
"category": "Python",
194194
"icon": "$(check)"
195195
},
196+
{
197+
"command": "python-envs.setEnvSelected",
198+
"title": "%python-envs.setEnvSelected.title%",
199+
"category": "Python",
200+
"icon": "$(pass-filled)"
201+
},
196202
{
197203
"command": "python-envs.remove",
198204
"title": "%python-envs.remove.title%",
@@ -272,12 +278,24 @@
272278
"category": "Python Envs",
273279
"icon": "$(copy)"
274280
},
281+
{
282+
"command": "python-envs.copyEnvPathCopied",
283+
"title": "%python-envs.copyEnvPathCopied.title%",
284+
"category": "Python Envs",
285+
"icon": "$(clippy)"
286+
},
275287
{
276288
"command": "python-envs.copyProjectPath",
277289
"title": "%python-envs.copyProjectPath.title%",
278290
"category": "Python Envs",
279291
"icon": "$(copy)"
280292
},
293+
{
294+
"command": "python-envs.copyProjectPathCopied",
295+
"title": "%python-envs.copyProjectPathCopied.title%",
296+
"category": "Python Envs",
297+
"icon": "$(clippy)"
298+
},
281299
{
282300
"command": "python-envs.terminal.revertStartupScriptChanges",
283301
"title": "%python-envs.terminal.revertStartupScriptChanges.title%",
@@ -321,6 +339,10 @@
321339
"command": "python-envs.setEnv",
322340
"when": "false"
323341
},
342+
{
343+
"command": "python-envs.setEnvSelected",
344+
"when": "false"
345+
},
324346
{
325347
"command": "python-envs.remove",
326348
"when": "false"
@@ -381,10 +403,18 @@
381403
"command": "python-envs.copyEnvPath",
382404
"when": "false"
383405
},
406+
{
407+
"command": "python-envs.copyEnvPathCopied",
408+
"when": "false"
409+
},
384410
{
385411
"command": "python-envs.copyProjectPath",
386412
"when": "false"
387413
},
414+
{
415+
"command": "python-envs.copyProjectPathCopied",
416+
"when": "false"
417+
},
388418
{
389419
"command": "python-envs.createAny",
390420
"when": "false"
@@ -419,7 +449,12 @@
419449
{
420450
"command": "python-envs.setEnv",
421451
"group": "inline",
422-
"when": "view == env-managers && viewItem =~ /.*pythonEnvironment.*/"
452+
"when": "view == env-managers && viewItem =~ /.*pythonEnvironment.*/ && viewItem =~ /^((?!selected).)*$/"
453+
},
454+
{
455+
"command": "python-envs.setEnvSelected",
456+
"group": "inline",
457+
"when": "view == env-managers && viewItem =~ /.*pythonEnvironment.*/ && viewItem =~ /.*selected.*/"
423458
},
424459
{
425460
"command": "python-envs.createTerminal",
@@ -438,7 +473,12 @@
438473
{
439474
"command": "python-envs.copyEnvPath",
440475
"group": "inline",
441-
"when": "view == env-managers && viewItem =~ /.*pythonEnvironment.*/"
476+
"when": "view == env-managers && viewItem =~ /.*pythonEnvironment.*/ && viewItem =~ /^((?!copied).)*$/"
477+
},
478+
{
479+
"command": "python-envs.copyEnvPathCopied",
480+
"group": "inline",
481+
"when": "view == env-managers && viewItem =~ /.*pythonEnvironment.*/ && viewItem =~ /.*copied.*/"
442482
},
443483
{
444484
"command": "python-envs.uninstallPackage",
@@ -448,7 +488,12 @@
448488
{
449489
"command": "python-envs.copyEnvPath",
450490
"group": "inline",
451-
"when": "view == python-projects && viewItem == python-env"
491+
"when": "view == python-projects && viewItem =~ /python-env/ && viewItem =~ /^((?!copied).)*$/"
492+
},
493+
{
494+
"command": "python-envs.copyEnvPathCopied",
495+
"group": "inline",
496+
"when": "view == python-projects && viewItem =~ /python-env/ && viewItem =~ /.*copied.*/"
452497
},
453498
{
454499
"command": "python-envs.remove",
@@ -471,7 +516,12 @@
471516
{
472517
"command": "python-envs.copyProjectPath",
473518
"group": "inline",
474-
"when": "view == python-projects && viewItem =~ /.*python-workspace.*/"
519+
"when": "view == python-projects && viewItem =~ /.*python-workspace.*/ && viewItem =~ /^((?!copied).)*$/"
520+
},
521+
{
522+
"command": "python-envs.copyProjectPathCopied",
523+
"group": "inline",
524+
"when": "view == python-projects && viewItem =~ /.*python-workspace.*/ && viewItem =~ /.*copied.*/"
475525
},
476526
{
477527
"command": "python-envs.revealProjectInExplorer",

package.nls.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
"python-envs.addPythonProjectGivenResource.title": "Add as Python Project",
2222
"python-envs.removePythonProject.title": "Remove Python Project",
2323
"python-envs.copyEnvPath.title": "Copy Environment Path",
24+
"python-envs.copyEnvPathCopied.title": "Copied!",
2425
"python-envs.copyProjectPath.title": "Copy Project Path",
26+
"python-envs.copyProjectPathCopied.title": "Copied!",
2527
"python-envs.create.title": "Create Environment",
2628
"python-envs.createAny.title": "Create Environment",
2729
"python-envs.set.title": "Set Project Environment",
2830
"python-envs.setEnv.title": "Set As Project Environment",
31+
"python-envs.setEnvSelected.title": "Set!",
2932
"python-envs.remove.title": "Delete Environment",
3033
"python-envs.refreshAllManagers.title": "Refresh All Environment Managers",
3134
"python-envs.refreshPackages.title": "Refresh Packages List",

src/extension.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ import { EnvManagerView } from './features/views/envManagersView';
6565
import { ProjectView } from './features/views/projectView';
6666
import { PythonStatusBarImpl } from './features/views/pythonStatusBar';
6767
import { updateViewsAndStatus } from './features/views/revealHandler';
68-
import { ProjectItem } from './features/views/treeViewItems';
68+
import { TemporaryStateManager } from './features/views/temporaryStateManager';
69+
import { ProjectItem, PythonEnvTreeItem } from './features/views/treeViewItems';
6970
import {
7071
collectEnvironmentInfo,
7172
getEnvManagerAndPackageManagerConfigLevels,
@@ -146,10 +147,14 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
146147
setPythonApi(envManagers, projectManager, projectCreators, terminalManager, envVarManager);
147148
const api = await getPythonApi();
148149
const sysPythonManager = createDeferred<SysPythonManager>();
149-
const managerView = new EnvManagerView(envManagers);
150+
151+
const temporaryStateManager = new TemporaryStateManager();
152+
context.subscriptions.push(temporaryStateManager);
153+
154+
const managerView = new EnvManagerView(envManagers, temporaryStateManager);
150155
context.subscriptions.push(managerView);
151156

152-
const workspaceView = new ProjectView(envManagers, projectManager);
157+
const workspaceView = new ProjectView(envManagers, projectManager, temporaryStateManager);
153158
context.subscriptions.push(workspaceView);
154159
workspaceView.initialize();
155160

@@ -224,6 +229,12 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
224229
}),
225230
commands.registerCommand('python-envs.setEnv', async (item) => {
226231
await setEnvironmentCommand(item, envManagers, projectManager);
232+
if (item instanceof PythonEnvTreeItem) {
233+
temporaryStateManager.setState(item.environment.envId.id, 'selected');
234+
}
235+
}),
236+
commands.registerCommand('python-envs.setEnvSelected', async () => {
237+
// No-op: This command is just for showing the feedback icon
227238
}),
228239
commands.registerCommand('python-envs.setEnvManager', async () => {
229240
await setEnvManagerCommand(envManagers, projectManager);
@@ -283,9 +294,21 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
283294
}),
284295
commands.registerCommand('python-envs.copyEnvPath', async (item) => {
285296
await copyPathToClipboard(item);
297+
if (item?.environment?.envId) {
298+
temporaryStateManager.setState(item.environment.envId.id, 'copied');
299+
}
300+
}),
301+
commands.registerCommand('python-envs.copyEnvPathCopied', () => {
302+
// No-op: provides the checkmark icon
286303
}),
287304
commands.registerCommand('python-envs.copyProjectPath', async (item) => {
288305
await copyPathToClipboard(item);
306+
if (item?.project?.uri) {
307+
temporaryStateManager.setState(item.project.uri.fsPath, 'copied');
308+
}
309+
}),
310+
commands.registerCommand('python-envs.copyProjectPathCopied', () => {
311+
// No-op: provides the checkmark icon
289312
}),
290313
commands.registerCommand('python-envs.revealProjectInExplorer', async (item) => {
291314
await revealProjectInExplorer(item);

src/features/views/envManagersView.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Disposable, Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeView, window } from 'vscode';
22
import { DidChangeEnvironmentEventArgs, EnvironmentGroupInfo, PythonEnvironment } from '../../api';
3+
import { ProjectViews } from '../../common/localize';
4+
import { createSimpleDebounce } from '../../common/utils/debounce';
35
import {
46
DidChangeEnvironmentManagerEventArgs,
57
DidChangePackageManagerEventArgs,
@@ -9,18 +11,21 @@ import {
911
InternalEnvironmentManager,
1012
InternalPackageManager,
1113
} from '../../internal.api';
14+
import { ITemporaryStateManager } from './temporaryStateManager';
1215
import {
13-
EnvTreeItem,
16+
EnvInfoTreeItem,
1417
EnvManagerTreeItem,
15-
PythonEnvTreeItem,
16-
PackageTreeItem,
18+
EnvTreeItem,
1719
EnvTreeItemKind,
1820
NoPythonEnvTreeItem,
19-
EnvInfoTreeItem,
21+
PackageTreeItem,
22+
PythonEnvTreeItem,
2023
PythonGroupEnvTreeItem,
2124
} from './treeViewItems';
22-
import { createSimpleDebounce } from '../../common/utils/debounce';
23-
import { ProjectViews } from '../../common/localize';
25+
26+
const COPIED_STATE = 'copied';
27+
const SELECTED_STATE = 'selected';
28+
const ENV_STATE_KEYS = [COPIED_STATE, SELECTED_STATE];
2429

2530
export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable {
2631
private treeView: TreeView<EnvTreeItem>;
@@ -32,7 +37,7 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
3237
private selected: Map<string, string> = new Map();
3338
private disposables: Disposable[] = [];
3439

35-
public constructor(public providers: EnvironmentManagers) {
40+
public constructor(public providers: EnvironmentManagers, private stateManager: ITemporaryStateManager) {
3641
this.treeView = window.createTreeView<EnvTreeItem>('env-managers', {
3742
treeDataProvider: this,
3843
});
@@ -59,6 +64,15 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
5964
this.onDidChangePackageManager(p);
6065
}),
6166
);
67+
68+
this.disposables.push(
69+
this.stateManager.onDidChangeState(({ itemId }) => {
70+
const view = this.revealMap.get(itemId);
71+
if (view) {
72+
this.fireDataChanged(view);
73+
}
74+
}),
75+
);
6276
}
6377

6478
dispose() {
@@ -77,6 +91,15 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
7791
onDidChangeTreeData: Event<void | EnvTreeItem | EnvTreeItem[] | null | undefined> = this.treeDataChanged.event;
7892

7993
getTreeItem(element: EnvTreeItem): TreeItem | Thenable<TreeItem> {
94+
if (element.kind === EnvTreeItemKind.environment && element instanceof PythonEnvTreeItem) {
95+
const itemId = element.environment.envId.id;
96+
const currentContext = element.treeItem.contextValue ?? '';
97+
element.treeItem.contextValue = this.stateManager.updateContextValue(
98+
itemId,
99+
currentContext,
100+
ENV_STATE_KEYS,
101+
);
102+
}
80103
return element.treeItem;
81104
}
82105

@@ -202,7 +225,7 @@ export class EnvManagerView implements TreeDataProvider<EnvTreeItem>, Disposable
202225

203226
private onDidChangePackages(args: InternalDidChangePackagesEventArgs) {
204227
const view = Array.from(this.revealMap.values()).find(
205-
(v) => v.environment.envId.id === args.environment.envId.id
228+
(v) => v.environment.envId.id === args.environment.envId.id,
206229
);
207230
if (view) {
208231
this.fireDataChanged(view);

src/features/views/projectView.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ProjectViews } from '../../common/localize';
1414
import { createSimpleDebounce } from '../../common/utils/debounce';
1515
import { onDidChangeConfiguration } from '../../common/workspace.apis';
1616
import { EnvironmentManagers, PythonProjectManager } from '../../internal.api';
17+
import { ITemporaryStateManager } from './temporaryStateManager';
1718
import {
1819
GlobalProjectItem,
1920
NoProjectEnvironment,
@@ -25,6 +26,10 @@ import {
2526
ProjectTreeItemKind,
2627
} from './treeViewItems';
2728

29+
const COPIED_STATE = 'copied';
30+
const SELECTED_STATE = 'selected';
31+
const ENV_STATE_KEYS = [COPIED_STATE, SELECTED_STATE];
32+
2833
export class ProjectView implements TreeDataProvider<ProjectTreeItem> {
2934
private treeView: TreeView<ProjectTreeItem>;
3035
private _treeDataChanged: EventEmitter<ProjectTreeItem | ProjectTreeItem[] | null | undefined> = new EventEmitter<
@@ -35,7 +40,11 @@ export class ProjectView implements TreeDataProvider<ProjectTreeItem> {
3540
private packageRoots: Map<string, ProjectEnvironment> = new Map();
3641
private disposables: Disposable[] = [];
3742
private debouncedUpdateProject = createSimpleDebounce(500, () => this.updateProject());
38-
public constructor(private envManagers: EnvironmentManagers, private projectManager: PythonProjectManager) {
43+
public constructor(
44+
private envManagers: EnvironmentManagers,
45+
private projectManager: PythonProjectManager,
46+
private stateManager: ITemporaryStateManager,
47+
) {
3948
this.treeView = window.createTreeView<ProjectTreeItem>('python-projects', {
4049
treeDataProvider: this,
4150
});
@@ -69,6 +78,20 @@ export class ProjectView implements TreeDataProvider<ProjectTreeItem> {
6978
}
7079
}),
7180
);
81+
82+
this.disposables.push(
83+
this.stateManager.onDidChangeState(({ itemId }) => {
84+
const projectView = this.projectViews.get(itemId);
85+
if (projectView) {
86+
this._treeDataChanged.fire(projectView);
87+
return;
88+
}
89+
const envView = Array.from(this.revealMap.values()).find((v) => v.environment.envId.id === itemId);
90+
if (envView) {
91+
this._treeDataChanged.fire(envView);
92+
}
93+
}),
94+
);
7295
}
7396

7497
initialize(): void {
@@ -121,6 +144,21 @@ export class ProjectView implements TreeDataProvider<ProjectTreeItem> {
121144
this._treeDataChanged.event;
122145

123146
getTreeItem(element: ProjectTreeItem): TreeItem | Thenable<TreeItem> {
147+
if (element.kind === ProjectTreeItemKind.project && element instanceof ProjectItem) {
148+
const itemId = element.project.uri.fsPath;
149+
const currentContext = element.treeItem.contextValue ?? '';
150+
element.treeItem.contextValue = this.stateManager.updateContextValue(itemId, currentContext, [
151+
COPIED_STATE,
152+
]);
153+
} else if (element.kind === ProjectTreeItemKind.environment && element instanceof ProjectEnvironment) {
154+
const itemId = element.environment.envId.id;
155+
const currentContext = element.treeItem.contextValue ?? '';
156+
element.treeItem.contextValue = this.stateManager.updateContextValue(
157+
itemId,
158+
currentContext,
159+
ENV_STATE_KEYS,
160+
);
161+
}
124162
return element.treeItem;
125163
}
126164

0 commit comments

Comments
 (0)