diff --git a/src/extension.ts b/src/extension.ts index c2630f58..113658d9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,7 @@ import { AutoFindProjects } from './features/creators/autoFindProjects'; import { ExistingProjects } from './features/creators/existingProjects'; import { ProjectCreatorsImpl } from './features/creators/projectCreators'; import { - addPythonProject, + addPythonProjectCommand, copyPathToClipboard, createAnyEnvironmentCommand, createEnvironmentCommand, @@ -185,7 +185,7 @@ export async function activate(context: ExtensionContext): Promise { - await addPythonProject(resource, projectManager, envManagers, projectCreators); + await addPythonProjectCommand(resource, projectManager, envManagers, projectCreators); }), commands.registerCommand('python-envs.removePythonProject', async (item) => { await resetEnvironmentCommand(item, envManagers, projectManager); diff --git a/src/features/creators/autoFindProjects.ts b/src/features/creators/autoFindProjects.ts index 5cef489c..3c7fd828 100644 --- a/src/features/creators/autoFindProjects.ts +++ b/src/features/creators/autoFindProjects.ts @@ -90,16 +90,19 @@ export class AutoFindProjects implements PythonProjectCreator { traceInfo(`Found ${filtered.length} new potential projects that aren't already registered`); - const projects = await pickProjects(filtered); - if (!projects || projects.length === 0) { + const projectUris = await pickProjects(filtered); + if (!projectUris || projectUris.length === 0) { // User cancelled the selection. traceInfo('User cancelled project selection.'); return; } - return projects.map((uri) => ({ + const projects = projectUris.map((uri) => ({ name: path.basename(uri.fsPath), uri, - })); + })) as PythonProject[]; + // Add the projects to the project manager + this.pm.add(projects); + return projects; } } diff --git a/src/features/creators/existingProjects.ts b/src/features/creators/existingProjects.ts index 9aa38c55..b30df92b 100644 --- a/src/features/creators/existingProjects.ts +++ b/src/features/creators/existingProjects.ts @@ -90,10 +90,13 @@ export class ExistingProjects implements PythonProjectCreator { } return; } else { - return resultsInWorkspace.map((uri) => ({ + const projects = resultsInWorkspace.map((uri) => ({ name: path.basename(uri.fsPath), uri, })) as PythonProject[]; + // Add the projects to the project manager + this.pm.add(projects); + return projects; } } } diff --git a/src/features/envCommands.ts b/src/features/envCommands.ts index ab83ecef..9ec9d7b9 100644 --- a/src/features/envCommands.ts +++ b/src/features/envCommands.ts @@ -1,53 +1,43 @@ import { QuickInputButtons, TaskExecution, TaskRevealKind, Terminal, Uri } from 'vscode'; -import { - EnvironmentManagers, - InternalEnvironmentManager, - InternalPackageManager, - ProjectCreators, - PythonProjectManager, -} from '../internal.api'; -import { traceError, traceInfo, traceVerbose } from '../common/logging'; import { CreateEnvironmentOptions, PythonEnvironment, PythonEnvironmentApi, - PythonProject, PythonProjectCreator, + PythonProjectCreatorOptions, } from '../api'; -import * as path from 'path'; +import { traceError, traceInfo, traceVerbose } from '../common/logging'; import { - setEnvironmentManager, - setPackageManager, - addPythonProjectSetting, - removePythonProjectSetting, - getDefaultEnvManagerSetting, - getDefaultPkgManagerSetting, - EditProjectSettings, -} from './settings/settingHelpers'; - -import { getAbsolutePath } from '../common/utils/fileNameUtils'; + EnvironmentManagers, + InternalEnvironmentManager, + InternalPackageManager, + ProjectCreators, + PythonProjectManager, +} from '../internal.api'; +import { removePythonProjectSetting, setEnvironmentManager, setPackageManager } from './settings/settingHelpers'; + +import { clipboardWriteText } from '../common/env.apis'; +import {} from '../common/errors/utils'; +import { pickEnvironment } from '../common/pickers/environments'; +import { pickCreator, pickEnvironmentManager, pickPackageManager } from '../common/pickers/managers'; +import { pickProject, pickProjectMany } from '../common/pickers/projects'; +import { activeTextEditor, showErrorMessage } from '../common/window.apis'; +import { quoteArgs } from './execution/execUtils'; import { runAsTask } from './execution/runAsTask'; +import { runInTerminal } from './terminal/runInTerminal'; +import { TerminalManager } from './terminal/terminalManager'; import { EnvManagerTreeItem, - PackageRootTreeItem, - PythonEnvTreeItem, - ProjectItem, - ProjectEnvironment, - ProjectPackageRootTreeItem, - GlobalProjectItem, EnvTreeItemKind, + GlobalProjectItem, + PackageRootTreeItem, PackageTreeItem, + ProjectEnvironment, + ProjectItem, ProjectPackage, + ProjectPackageRootTreeItem, + PythonEnvTreeItem, } from './views/treeViewItems'; -import { pickEnvironment } from '../common/pickers/environments'; -import { pickEnvironmentManager, pickPackageManager, pickCreator } from '../common/pickers/managers'; -import { pickProject, pickProjectMany } from '../common/pickers/projects'; -import { TerminalManager } from './terminal/terminalManager'; -import { runInTerminal } from './terminal/runInTerminal'; -import { quoteArgs } from './execution/execUtils'; -import {} from '../common/errors/utils'; -import { activeTextEditor, showErrorMessage } from '../common/window.apis'; -import { clipboardWriteText } from '../common/env.apis'; export async function refreshManagerCommand(context: unknown): Promise { if (context instanceof EnvManagerTreeItem) { @@ -338,93 +328,61 @@ export async function setPackageManagerCommand(em: EnvironmentManagers, wm: Pyth } } -export async function addPythonProject( +/** + * Creates a new Python project using a selected PythonProjectCreator. + * + * This function calls create on the selected creator and handles the creation process. Will return + * without doing anything if the resource is a ProjectItem, as the project is already created. + * + * @param resource - The resource to use for project creation (can be a Uri(s), ProjectItem(s), or undefined). + * @param wm - The PythonProjectManager instance for managing projects. + * @param em - The EnvironmentManagers instance for managing environments. + * @param pc - The ProjectCreators instance for accessing available project creators. + * @returns A promise that resolves when the project has been created, or void if cancelled or invalid. + */ +export async function addPythonProjectCommand( resource: unknown, wm: PythonProjectManager, em: EnvironmentManagers, pc: ProjectCreators, -): Promise { +): Promise { if (wm.getProjects().length === 0) { showErrorMessage('Please open a folder/project before adding a workspace'); return; } - - if (resource instanceof Uri) { - const uri = resource as Uri; - const envManagerId = getDefaultEnvManagerSetting(wm, uri); - const pkgManagerId = getDefaultPkgManagerSetting( - wm, - uri, - em.getEnvironmentManager(envManagerId)?.preferredPackageManagerId, - ); - const pw = wm.create(path.basename(uri.fsPath), uri); - await addPythonProjectSetting([{ project: pw, envManager: envManagerId, packageManager: pkgManagerId }]); - return pw; - } - - if (resource === undefined || resource instanceof ProjectItem) { - const creator: PythonProjectCreator | undefined = await pickCreator(pc.getProjectCreators()); - if (!creator) { + if (resource instanceof Array) { + for (const r of resource) { + await addPythonProjectCommand(r, wm, em, pc); return; } + } + if (resource instanceof ProjectItem) { + // If the context is a ProjectItem, project is already created. Just add it to the package manager project list. + wm.add(resource.project); + return; + } + let options: PythonProjectCreatorOptions | undefined; - let results: PythonProject | PythonProject[] | Uri | Uri[] | undefined; - try { - results = await creator.create(); - if (results === undefined) { - return; - } - } catch (ex) { - if (ex === QuickInputButtons.Back) { - return addPythonProject(resource, wm, em, pc); - } - throw ex; - } - - if ( - results instanceof Uri || - (Array.isArray(results) && results.length > 0 && results.every((r) => r instanceof Uri)) - ) { - // the results are Uris, which means they aren't projects and shouldn't be added - return; - } - results = results as PythonProject | PythonProject[]; - - if (!Array.isArray(results)) { - results = [results]; - } - - if (Array.isArray(results)) { - if (results.length === 0) { - return; - } - } - - const projects: PythonProject[] = []; - const edits: EditProjectSettings[] = []; + if (resource instanceof Uri) { + // Use resource as the URI for the project if it is a URI. + options = { + name: resource.fsPath, + rootUri: resource, + }; + } - for (const result of results) { - const uri = await getAbsolutePath(result.uri.fsPath); - if (!uri) { - traceError(`Path does not belong to any opened workspace: ${result.uri.fsPath}`); - continue; - } + const creator: PythonProjectCreator | undefined = await pickCreator(pc.getProjectCreators()); + if (!creator) { + return; + } - const envManagerId = getDefaultEnvManagerSetting(wm, uri); - const pkgManagerId = getDefaultPkgManagerSetting( - wm, - uri, - em.getEnvironmentManager(envManagerId)?.preferredPackageManagerId, - ); - const pw = wm.create(path.basename(uri.fsPath), uri); - projects.push(pw); - edits.push({ project: pw, envManager: envManagerId, packageManager: pkgManagerId }); + try { + await creator.create(options); + } catch (ex) { + if (ex === QuickInputButtons.Back) { + return addPythonProjectCommand(resource, wm, em, pc); } - await addPythonProjectSetting(edits); - return projects; - } else { - // If the context is not a Uri or ProjectItem, rerun function with undefined context - await addPythonProject(undefined, wm, em, pc); + throw ex; } } diff --git a/src/features/projectManager.ts b/src/features/projectManager.ts index 0316f4fb..095be208 100644 --- a/src/features/projectManager.ts +++ b/src/features/projectManager.ts @@ -9,6 +9,12 @@ import { onDidChangeWorkspaceFolders, } from '../common/workspace.apis'; import { createSimpleDebounce } from '../common/utils/debounce'; +import { + addPythonProjectSetting, + EditProjectSettings, + getDefaultEnvManagerSetting, + getDefaultPkgManagerSetting, +} from './settings/settingHelpers'; type ProjectArray = PythonProject[]; @@ -92,14 +98,24 @@ export class PythonProjectManagerImpl implements PythonProjectManager { return new PythonProjectsImpl(name, uri, options); } - add(projects: PythonProject | ProjectArray): void { + async add(projects: PythonProject | ProjectArray): Promise { const _projects = Array.isArray(projects) ? projects : [projects]; if (_projects.length === 0) { return; } + const edits: EditProjectSettings[] = []; + + const envManagerId = getDefaultEnvManagerSetting(this); + const pkgManagerId = getDefaultPkgManagerSetting(this); - _projects.forEach((w) => this._projects.set(w.uri.toString(), w)); + _projects.forEach((w) => { + edits.push({ project: w, envManager: envManagerId, packageManager: pkgManagerId }); + return this._projects.set(w.uri.toString(), w); + }); this._onDidChangeProjects.fire(Array.from(this._projects.values())); + + // handle bulk edits to avoid multiple calls to the setting + await addPythonProjectSetting(edits); } remove(projects: PythonProject | ProjectArray): void { diff --git a/src/internal.api.ts b/src/internal.api.ts index 509d48ac..668fe3fa 100644 --- a/src/internal.api.ts +++ b/src/internal.api.ts @@ -284,7 +284,7 @@ export interface PythonProjectManager extends Disposable { uri: Uri, options?: { description?: string; tooltip?: string | MarkdownString; iconPath?: IconPath }, ): PythonProject; - add(pyWorkspace: PythonProject | PythonProject[]): void; + add(pyWorkspace: PythonProject | PythonProject[]): Promise; remove(pyWorkspace: PythonProject | PythonProject[]): void; getProjects(uris?: Uri[]): ReadonlyArray; get(uri: Uri): PythonProject | undefined;