Skip to content

Commit 53d7804

Browse files
authored
Add support for Global namespace (#35)
Fixes #31 Fixes #32
1 parent fe08161 commit 53d7804

File tree

12 files changed

+206
-92
lines changed

12 files changed

+206
-92
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@
310310
},
311311
{
312312
"command": "python-envs.removePythonProject",
313-
"when": "view == python-projects && viewItem == python-workspace"
313+
"when": "view == python-projects && viewItem == python-workspace-removable"
314314
},
315315
{
316316
"command": "python-envs.set",

src/common/utils/fileNameUtils.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as path from 'path';
22
import * as fsapi from 'fs-extra';
33
import { KNOWN_FILES, KNOWN_TEMPLATE_ENDINGS } from '../constants';
4-
import { Uri, workspace } from 'vscode';
4+
import { Uri } from 'vscode';
5+
import { getWorkspaceFolders } from '../workspace.apis';
56

67
export function isPythonProjectFile(fileName: string): boolean {
78
const baseName = path.basename(fileName).toLowerCase();
@@ -20,14 +21,15 @@ export async function getAbsolutePath(fsPath: string): Promise<Uri | undefined>
2021
return Uri.file(fsPath);
2122
}
2223

23-
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
24-
if (workspace.workspaceFolders.length === 1) {
25-
const absPath = path.resolve(workspace.workspaceFolders[0].uri.fsPath, fsPath);
24+
const workspaceFolders = getWorkspaceFolders() ?? [];
25+
if (workspaceFolders.length > 0) {
26+
if (workspaceFolders.length === 1) {
27+
const absPath = path.resolve(workspaceFolders[0].uri.fsPath, fsPath);
2628
if (await fsapi.pathExists(absPath)) {
2729
return Uri.file(absPath);
2830
}
2931
} else {
30-
const workspaces = Array.from(workspace.workspaceFolders)
32+
const workspaces = Array.from(workspaceFolders)
3133
.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)
3234
.reverse();
3335
for (const folder of workspaces) {

src/extension.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
109109
commands.registerCommand('python-envs.create', async (item) => {
110110
return await createEnvironmentCommand(item, envManagers, projectManager);
111111
}),
112-
commands.registerCommand('python-envs.createAny', async () => {
113-
return await createAnyEnvironmentCommand(envManagers, projectManager);
112+
commands.registerCommand('python-envs.createAny', async (options) => {
113+
return await createAnyEnvironmentCommand(
114+
envManagers,
115+
projectManager,
116+
options ?? { selectEnvironment: true },
117+
);
114118
}),
115119
commands.registerCommand('python-envs.remove', async (item) => {
116120
await removeEnvironmentCommand(item, envManagers);

src/features/envCommands.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
ProjectCreators,
77
PythonProjectManager,
88
} from '../internal.api';
9-
import { traceError, traceVerbose } from '../common/logging';
9+
import { traceError, traceInfo, traceVerbose } from '../common/logging';
1010
import { PythonEnvironment, PythonEnvironmentApi, PythonProject, PythonProjectCreator } from '../api';
1111
import * as path from 'path';
1212
import {
@@ -71,11 +71,25 @@ export async function createEnvironmentCommand(
7171
): Promise<PythonEnvironment | undefined> {
7272
if (context instanceof EnvManagerTreeItem) {
7373
const manager = (context as EnvManagerTreeItem).manager;
74-
const projects = await pickProjectMany(pm.getProjects());
75-
if (projects) {
76-
return await manager.create(projects.length === 0 ? 'global' : projects.map((p) => p.uri));
77-
} else {
78-
traceError(`No projects found for ${context}`);
74+
const projects = pm.getProjects();
75+
if (projects.length === 0) {
76+
const env = await manager.create('global');
77+
if (env) {
78+
await em.setEnvironments('global', env);
79+
}
80+
return env;
81+
} else if (projects.length > 0) {
82+
const selected = await pickProjectMany(projects);
83+
if (selected) {
84+
const scope = selected.length === 0 ? 'global' : selected.map((p) => p.uri);
85+
const env = await manager.create(scope);
86+
if (env) {
87+
await em.setEnvironments(scope, env);
88+
}
89+
return env;
90+
} else {
91+
traceInfo('No project selected or global condition met for environment creation');
92+
}
7993
}
8094
} else if (context instanceof Uri) {
8195
const manager = em.getEnvironmentManager(context as Uri);
@@ -93,7 +107,10 @@ export async function createEnvironmentCommand(
93107
export async function createAnyEnvironmentCommand(
94108
em: EnvironmentManagers,
95109
pm: PythonProjectManager,
110+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
111+
options?: any,
96112
): Promise<PythonEnvironment | undefined> {
113+
const select = options?.selectEnvironment;
97114
const projects = await pickProjectMany(pm.getProjects());
98115
if (projects && projects.length > 0) {
99116
const defaultManagers: InternalEnvironmentManager[] = [];
@@ -112,14 +129,25 @@ export async function createAnyEnvironmentCommand(
112129

113130
const manager = em.managers.find((m) => m.id === managerId);
114131
if (manager) {
115-
return await manager.create(projects.map((p) => p.uri));
132+
const env = await manager.create(projects.map((p) => p.uri));
133+
if (select) {
134+
await em.setEnvironments(
135+
projects.map((p) => p.uri),
136+
env,
137+
);
138+
}
139+
return env;
116140
}
117141
} else if (projects && projects.length === 0) {
118142
const managerId = await pickEnvironmentManager(em.managers.filter((m) => m.supportsCreate));
119143

120144
const manager = em.managers.find((m) => m.id === managerId);
121145
if (manager) {
122-
return await manager.create('global');
146+
const env = await manager.create('global');
147+
if (select) {
148+
await manager.set(undefined, env);
149+
}
150+
return env;
123151
}
124152
}
125153
}
@@ -203,10 +231,24 @@ export async function setEnvironmentCommand(
203231
await setEnvironmentCommand([context], em, wm);
204232
} else if (context === undefined) {
205233
try {
206-
const projects = await pickProjectMany(wm.getProjects());
234+
const projects = wm.getProjects();
207235
if (projects && projects.length > 0) {
208-
const uris = projects.map((p) => p.uri);
209-
await setEnvironmentCommand(uris, em, wm);
236+
const selected = await pickProjectMany(projects);
237+
if (selected && selected.length > 0) {
238+
const uris = selected.map((p) => p.uri);
239+
await setEnvironmentCommand(uris, em, wm);
240+
}
241+
} else {
242+
const globalEnvManager = em.getEnvironmentManager(undefined);
243+
const recommended = globalEnvManager ? await globalEnvManager.get(undefined) : undefined;
244+
const selected = await pickEnvironment(em.managers, globalEnvManager ? [globalEnvManager] : [], {
245+
projects: [],
246+
recommended,
247+
showBackButton: false,
248+
});
249+
if (selected) {
250+
await em.setEnvironments('global', selected);
251+
}
210252
}
211253
} catch (ex) {
212254
if (ex === QuickInputButtons.Back) {
@@ -487,7 +529,7 @@ export async function runAsTaskCommand(item: unknown, api: PythonEnvironmentApi)
487529
const uri = item as Uri;
488530
const project = api.getPythonProject(uri);
489531
const environment = await api.getEnvironment(uri);
490-
if (environment && project) {
532+
if (environment) {
491533
return await runAsTask(
492534
environment,
493535
{

src/features/envManagers.ts

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
272272
}
273273
}
274274

275-
public async setEnvironments(scope: Uri[], environment?: PythonEnvironment): Promise<void> {
275+
public async setEnvironments(scope: Uri[] | string, environment?: PythonEnvironment): Promise<void> {
276276
if (environment) {
277277
const manager = this.managers.find((m) => m.id === environment.envId.managerId);
278278
if (!manager) {
@@ -287,50 +287,87 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
287287
const promises: Promise<void>[] = [];
288288
const settings: EditAllManagerSettings[] = [];
289289
const events: DidChangeEnvironmentEventArgs[] = [];
290-
scope.forEach((uri) => {
291-
const m = this.getEnvironmentManager(uri);
292-
promises.push(manager.set(uri, environment));
290+
if (Array.isArray(scope) && scope.every((s) => s instanceof Uri)) {
291+
scope.forEach((uri) => {
292+
const m = this.getEnvironmentManager(uri);
293+
promises.push(manager.set(uri, environment));
294+
if (manager.id !== m?.id) {
295+
settings.push({
296+
project: this.pm.get(uri),
297+
envManager: manager.id,
298+
packageManager: manager.preferredPackageManagerId,
299+
});
300+
}
301+
302+
const project = this.pm.get(uri);
303+
const oldEnv = this._previousEnvironments.get(project?.uri.toString() ?? 'global');
304+
if (oldEnv?.envId.id !== environment?.envId.id) {
305+
this._previousEnvironments.set(project?.uri.toString() ?? 'global', environment);
306+
events.push({ uri: project?.uri, new: environment, old: oldEnv });
307+
}
308+
});
309+
} else if (typeof scope === 'string' && scope === 'global') {
310+
const m = this.getEnvironmentManager(undefined);
311+
promises.push(manager.set(undefined, environment));
293312
if (manager.id !== m?.id) {
294313
settings.push({
295-
project: this.pm.get(uri),
314+
project: undefined,
296315
envManager: manager.id,
297316
packageManager: manager.preferredPackageManagerId,
298317
});
299318
}
300319

301-
const project = this.pm.get(uri);
302-
const oldEnv = this._previousEnvironments.get(project?.uri.toString() ?? 'global');
320+
const oldEnv = this._previousEnvironments.get('global');
303321
if (oldEnv?.envId.id !== environment?.envId.id) {
304-
this._previousEnvironments.set(project?.uri.toString() ?? 'global', environment);
305-
events.push({ uri: project?.uri, new: environment, old: oldEnv });
322+
this._previousEnvironments.set('global', environment);
323+
events.push({ uri: undefined, new: environment, old: oldEnv });
306324
}
307-
});
325+
}
308326
await Promise.all(promises);
309327
await setAllManagerSettings(settings);
310328
setImmediate(() => events.forEach((e) => this._onDidChangeEnvironmentFiltered.fire(e)));
311329
} else {
312330
const promises: Promise<void>[] = [];
313331
const events: DidChangeEnvironmentEventArgs[] = [];
314-
scope.forEach((uri) => {
315-
const manager = this.getEnvironmentManager(uri);
332+
if (Array.isArray(scope) && scope.every((s) => s instanceof Uri)) {
333+
scope.forEach((uri) => {
334+
const manager = this.getEnvironmentManager(uri);
335+
if (manager) {
336+
const setAndAddEvent = async () => {
337+
await manager.set(uri);
338+
339+
const project = this.pm.get(uri);
340+
341+
// Always get the new first, then compare with the old. This has minor impact on the ordering of
342+
// events. But it ensures that we always get the latest environment at the time of this call.
343+
const newEnv = await manager.get(uri);
344+
const oldEnv = this._previousEnvironments.get(project?.uri.toString() ?? 'global');
345+
if (oldEnv?.envId.id !== newEnv?.envId.id) {
346+
this._previousEnvironments.set(project?.uri.toString() ?? 'global', newEnv);
347+
events.push({ uri: project?.uri, new: newEnv, old: oldEnv });
348+
}
349+
};
350+
promises.push(setAndAddEvent());
351+
}
352+
});
353+
} else if (typeof scope === 'string' && scope === 'global') {
354+
const manager = this.getEnvironmentManager(undefined);
316355
if (manager) {
317356
const setAndAddEvent = async () => {
318-
await manager.set(uri);
319-
320-
const project = this.pm.get(uri);
357+
await manager.set(undefined);
321358

322359
// Always get the new first, then compare with the old. This has minor impact on the ordering of
323360
// events. But it ensures that we always get the latest environment at the time of this call.
324-
const newEnv = await manager.get(uri);
325-
const oldEnv = this._previousEnvironments.get(project?.uri.toString() ?? 'global');
361+
const newEnv = await manager.get(undefined);
362+
const oldEnv = this._previousEnvironments.get('global');
326363
if (oldEnv?.envId.id !== newEnv?.envId.id) {
327-
this._previousEnvironments.set(project?.uri.toString() ?? 'global', newEnv);
328-
events.push({ uri: project?.uri, new: newEnv, old: oldEnv });
364+
this._previousEnvironments.set('global', newEnv);
365+
events.push({ uri: undefined, new: newEnv, old: oldEnv });
329366
}
330367
};
331368
promises.push(setAndAddEvent());
332369
}
333-
});
370+
}
334371
await Promise.all(promises);
335372
setImmediate(() => events.forEach((e) => this._onDidChangeEnvironmentFiltered.fire(e)));
336373
}

src/features/settings/settingHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export async function setAllManagerSettings(edits: EditAllManagerSettings[]): Pr
101101

102102
noWorkspace.forEach((e) => {
103103
if (e.project) {
104-
traceError(`Unable to find workspace for ${e.project.uri.fsPath}`);
104+
traceInfo(`Unable to find workspace for ${e.project.uri.fsPath}, will use global settings for this.`);
105105
}
106106
});
107107

src/features/views/projectView.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
ProjectEnvironmentInfo,
2222
ProjectPackage,
2323
ProjectPackageRootInfoTreeItem,
24+
GlobalProjectItem,
2425
} from './treeViewItems';
2526
import { onDidChangeConfiguration } from '../../common/workspace.apis';
2627
import { createSimpleDebounce } from '../../common/utils/debounce';
@@ -100,12 +101,11 @@ export class WorkspaceView implements TreeDataProvider<ProjectTreeItem> {
100101
reveal(context: Uri | PythonEnvironment): PythonEnvironment | undefined {
101102
if (context instanceof Uri) {
102103
const pw = this.projectManager.get(context);
103-
if (pw) {
104-
const view = this.revealMap.get(pw.uri.fsPath);
105-
if (view) {
106-
this.revealInternal(view);
107-
return view.environment;
108-
}
104+
const key = pw ? pw.uri.fsPath : 'global';
105+
const view = this.revealMap.get(key);
106+
if (view) {
107+
this.revealInternal(view);
108+
return view.environment;
109109
}
110110
} else {
111111
const view = Array.from(this.revealMap.values()).find((v) => v.environment.envId.id === context.envId.id);
@@ -128,12 +128,17 @@ export class WorkspaceView implements TreeDataProvider<ProjectTreeItem> {
128128
if (element === undefined) {
129129
this.projectViews.clear();
130130
const views: ProjectTreeItem[] = [];
131-
this.projectManager.getProjects().forEach((w) => {
131+
const projects = this.projectManager.getProjects();
132+
projects.forEach((w) => {
132133
const view = new ProjectItem(w);
133134
this.projectViews.set(w.uri.fsPath, view);
134135
views.push(view);
135136
});
136137

138+
if (projects.length === 0) {
139+
views.push(new GlobalProjectItem());
140+
}
141+
137142
return views;
138143
}
139144

@@ -152,7 +157,8 @@ export class WorkspaceView implements TreeDataProvider<ProjectTreeItem> {
152157
];
153158
}
154159

155-
const manager = this.envManagers.getEnvironmentManager(projectItem.project.uri);
160+
const uri = projectItem.id === 'global' ? undefined : projectItem.project.uri;
161+
const manager = this.envManagers.getEnvironmentManager(uri);
156162
if (!manager) {
157163
return [
158164
new NoProjectEnvironment(
@@ -164,7 +170,7 @@ export class WorkspaceView implements TreeDataProvider<ProjectTreeItem> {
164170
];
165171
}
166172

167-
const environment = await manager?.get(projectItem.project.uri);
173+
const environment = await manager?.get(uri);
168174
if (!environment) {
169175
return [
170176
new NoProjectEnvironment(
@@ -175,7 +181,7 @@ export class WorkspaceView implements TreeDataProvider<ProjectTreeItem> {
175181
];
176182
}
177183
const view = new ProjectEnvironment(projectItem, environment);
178-
this.revealMap.set(projectItem.project.uri.fsPath, view);
184+
this.revealMap.set(uri ? uri.fsPath : 'global', view);
179185
return [view];
180186
}
181187

0 commit comments

Comments
 (0)