Skip to content

Commit c6f762b

Browse files
committed
create venv support
1 parent b475dc6 commit c6f762b

3 files changed

Lines changed: 71 additions & 10 deletions

File tree

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
9393
projectCreators,
9494
projectCreators.registerPythonProjectCreator(new ExistingProjects(projectManager)),
9595
projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager)),
96-
projectCreators.registerPythonProjectCreator(new NewPackageProject()),
96+
projectCreators.registerPythonProjectCreator(new NewPackageProject(envManagers)),
9797
projectCreators.registerPythonProjectCreator(new NewScriptProject()),
9898
);
9999

src/features/creators/creationHelpers.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { window, extensions } from 'vscode';
1+
import { window, extensions, Uri } from 'vscode';
22
import * as fs from 'fs-extra';
33
import * as path from 'path';
4+
import { EnvironmentManagers } from '../../internal.api';
5+
import { CreateEnvironmentOptions, EnvironmentManager } from '../../api';
6+
import { traceVerbose } from '../../common/logging';
47

58
/**
69
* Prompts the user to choose whether to create a new venv for a package.
@@ -49,17 +52,56 @@ export async function removeCopilotInstructions(destFolder: string) {
4952
}
5053
}
5154

52-
export async function quickCreateNewVenv(destFolder: string) {
53-
const venvPath = path.join(destFolder, '.venv');
55+
export async function quickCreateNewVenv(envManagers: EnvironmentManagers, destFolder: string) {
5456
try {
55-
// Placeholder: replace with your venv creation logic
56-
// await quickCreateVenv(...)
57-
window.showInformationMessage(`(Placeholder) Would create venv at: ${venvPath}`);
57+
// get the environment manager for venv
58+
const envManager: EnvironmentManager | undefined = envManagers.managers.find(
59+
(m) => m.id === 'ms-python.python:venv',
60+
);
61+
const destUri = Uri.parse(destFolder);
62+
if (envManager && envManager.create) {
63+
// with quickCreate enabled, user will not be prompted when creating the environment
64+
const options: CreateEnvironmentOptions = { quickCreate: false };
65+
if (envManager.quickCreateConfig) {
66+
options.quickCreate = true;
67+
}
68+
const pyEnv = await envManager.create(destUri, options);
69+
// comes back as undefined if this doesn't work
70+
traceVerbose(`Created venv at: ${pyEnv?.environmentPath} using ${envManager.name}`);
71+
} else {
72+
// find an environment manager that supports create
73+
const envManager = envManagers.managers.find((m) => m.create);
74+
if (envManager) {
75+
const pyEnv = await envManager.create(destUri, {});
76+
traceVerbose(`Created venv at: ${pyEnv?.environmentPath} using ${envManager.name}`);
77+
}
78+
// If no environment manager supports create, show an error message
79+
window.showErrorMessage(
80+
`No environment manager found that supports creating a new environment, skipping...`,
81+
);
82+
}
5883
} catch (err) {
5984
window.showErrorMessage(`Failed to create virtual environment: ${err}`);
6085
}
6186
}
6287

88+
/**
89+
* Replaces all occurrences of a string in a single file's contents, handling special regex characters in the search value.
90+
* @param filePath The path to the file to update.
91+
* @param searchValue The string to search for (will be escaped for regex).
92+
* @param replaceValue The string to replace with.
93+
*/
94+
export async function replaceInFile(filePath: string, searchValue: string, replaceValue: string) {
95+
// Escape special regex characters in searchValue
96+
const escapedSearchValue = searchValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
97+
const regex = new RegExp(escapedSearchValue, 'g');
98+
let content = await fs.readFile(filePath, 'utf8');
99+
if (content.includes(searchValue)) {
100+
content = content.replace(regex, replaceValue);
101+
await fs.writeFile(filePath, content, 'utf8');
102+
}
103+
}
104+
63105
// Helper to recursively replace all occurrences of a string in file/folder names and file contents
64106
export async function replaceInFilesAndNames(dir: string, searchValue: string, replaceValue: string) {
65107
const entries = await fs.readdir(dir, { withFileTypes: true });

src/features/creators/newPackageProject.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ import {
99
quickCreateNewVenv,
1010
removeCopilotInstructions,
1111
replaceInFilesAndNames,
12+
replaceInFile,
1213
} from './creationHelpers';
1314
import { EXTENSION_ROOT_DIR } from '../../common/constants';
15+
import { EnvironmentManagers } from '../../internal.api';
1416

1517
export class NewPackageProject implements PythonProjectCreator {
1618
public readonly name = 'newPackage';
1719
public readonly displayName = 'Package';
1820
public readonly description = 'Create a new Python package';
1921
public readonly tooltip = new MarkdownString('Create a new Python package');
2022

21-
constructor() {}
23+
constructor(private readonly envManagers: EnvironmentManagers) {}
2224

2325
async create(_options?: PythonProjectCreatorOptions): Promise<PythonProject | undefined> {
2426
// Prompt for package name
@@ -30,7 +32,7 @@ export class NewPackageProject implements PythonProjectCreator {
3032
return undefined;
3133
}
3234

33-
// Use helper for venv
35+
// Use helper to prompt for virtual environment creation
3436
const createVenv = await promptForVenv();
3537
if (createVenv === undefined) {
3638
return undefined;
@@ -83,9 +85,26 @@ export class NewPackageProject implements PythonProjectCreator {
8385

8486
// 4. Create virtual environment if requested
8587
if (createVenv) {
86-
await quickCreateNewVenv(destFolder);
88+
await quickCreateNewVenv(this.envManagers, destFolder);
8789
}
8890

91+
// 5. Replace <run_exec> and <activation_command> in README.md
92+
const readmeFilePath = path.join(destFolder, 'README.md');
93+
const pythonEnvironment = await this.envManagers.getEnvironment(Uri.parse(destFolder));
94+
if (!pythonEnvironment) {
95+
window.showErrorMessage('Python environment not found.');
96+
return undefined;
97+
}
98+
const execInfo = pythonEnvironment.execInfo;
99+
if (execInfo.run) {
100+
let execRunStr = execInfo.run.executable;
101+
if (execInfo.run.args) {
102+
execRunStr += ` ${execInfo.run.args.join(' ')}`;
103+
}
104+
await replaceInFile(readmeFilePath, '<run_exec>', execRunStr);
105+
}
106+
// TODO: replace <activation_command> in README.md ?
107+
89108
// Return a PythonProject object if needed by your API
90109
return {
91110
name: packageName,

0 commit comments

Comments
 (0)