Skip to content

Commit 14f3dbf

Browse files
authored
Merge branch 'main' into create-project-branch
2 parents 7bf0208 + 1a96c22 commit 14f3dbf

File tree

16 files changed

+733
-37
lines changed

16 files changed

+733
-37
lines changed

package.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -516,11 +516,13 @@
516516
"languageModelTools": [
517517
{
518518
"name": "python_environment",
519-
"userDescription": "Get Python environment info for a file or path, including version, packages, and the command to run it.",
519+
"userDescription": "%python.languageModelTools.python_environment.userDescription%",
520520
"displayName": "Get Python Environment Information",
521521
"modelDescription": "Provides details about the Python environment for a specified file or workspace, including environment type, Python version, run command, and installed packages with their versions. Use this tool to determine the correct command for executing Python code in this workspace.",
522522
"toolReferenceName": "pythonGetEnvironmentInfo",
523-
"tags": [],
523+
"tags": [
524+
"ms-python.python"
525+
],
524526
"icon": "$(files)",
525527
"canBeReferencedInPrompt": true,
526528
"inputSchema": {
@@ -539,9 +541,12 @@
539541
{
540542
"name": "python_install_package",
541543
"displayName": "Install Python Package",
544+
"userDescription": "%python.languageModelTools.python_install_package.userDescription%",
542545
"modelDescription": "Installs Python packages in the given workspace. Use this tool to install packages in the user's chosen environment.",
543546
"toolReferenceName": "pythonInstallPackage",
544-
"tags": [],
547+
"tags": [
548+
"ms-python.python"
549+
],
545550
"icon": "$(package)",
546551
"canBeReferencedInPrompt": true,
547552
"inputSchema": {
@@ -613,4 +618,4 @@
613618
"vscode-jsonrpc": "^9.0.0-next.5",
614619
"which": "^4.0.0"
615620
}
616-
}
621+
}

package.nls.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@
3434
"python-envs.createNewProjectFromTemplate.title": "Create New Project from Template",
3535
"python-envs.terminal.activate.title": "Activate Environment in Current Terminal",
3636
"python-envs.terminal.deactivate.title": "Deactivate Environment in Current Terminal",
37-
"python-envs.uninstallPackage.title": "Uninstall Package"
38-
}
37+
"python-envs.uninstallPackage.title": "Uninstall Package",
38+
"python.languageModelTools.python_environment.userDescription": "Get Python environment info for a file or path, including version, packages, and the command to run it.",
39+
"python.languageModelTools.python_install_package.userDescription": "Installs Python packages in the given workspace."
40+
}

src/common/localize.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ export namespace CondaStrings {
152152
);
153153
}
154154

155+
export namespace PyenvStrings {
156+
export const pyenvManager = l10n.t('Manages Pyenv Python versions');
157+
export const pyenvDiscovering = l10n.t('Discovering Pyenv Python versions');
158+
export const pyenvRefreshing = l10n.t('Refreshing Pyenv Python versions');
159+
}
160+
155161
export namespace ProjectCreatorString {
156162
export const addExistingProjects = l10n.t('Add Existing Projects');
157163
export const autoFindProjects = l10n.t('Auto Find Projects');

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { EnvironmentManagers, ProjectCreators, PythonProjectManager } from './in
6868
import { registerSystemPythonFeatures } from './managers/builtin/main';
6969
import { createNativePythonFinder, NativePythonFinder } from './managers/common/nativePythonFinder';
7070
import { registerCondaFeatures } from './managers/conda/main';
71+
import { registerPyenvFeatures } from './managers/pyenv/main';
7172

7273
export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi> {
7374
const start = new StopWatch();
@@ -304,6 +305,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
304305
await Promise.all([
305306
registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel),
306307
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel),
308+
registerPyenvFeatures(nativeFinder, context.subscriptions),
307309
shellStartupVarsMgr.initialize(),
308310
]);
309311

src/features/copilotTools.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
CancellationToken,
3+
l10n,
34
LanguageModelTextPart,
45
LanguageModelTool,
56
LanguageModelToolInvocationOptions,
@@ -130,21 +131,16 @@ export class GetEnvironmentInfoTool implements LanguageModelTool<IResourceRefere
130131
_options: LanguageModelToolInvocationPrepareOptions<IResourceReference>,
131132
_token: CancellationToken,
132133
): Promise<PreparedToolInvocation> {
133-
const message = 'Preparing to fetch Python environment information...';
134134
return {
135-
invocationMessage: message,
135+
invocationMessage: l10n.t('Fetching Python environment information'),
136136
};
137137
}
138138
}
139139

140140
function BuildEnvironmentInfoContent(envInfo: EnvironmentInfo): LanguageModelTextPart {
141141
// Create a formatted string that looks like JSON but preserves comments
142-
let envTypeDescriptor: string = `This environment is managed by ${envInfo.type} environment manager. Use the install tool to install packages into this environment.`;
142+
const envTypeDescriptor: string = `This environment is managed by ${envInfo.type} environment manager. Use the install tool to install packages into this environment.`;
143143

144-
if (envInfo.type === 'system') {
145-
envTypeDescriptor =
146-
'System pythons are pythons that ship with the OS or are installed globally. These python installs may be used by the OS for running services and core functionality. Confirm with the user before installing packages into this environment, as it can lead to issues with any services on the OS.';
147-
}
148144
const content = `{
149145
// ${JSON.stringify(envTypeDescriptor)}
150146
"environmentType": ${JSON.stringify(envInfo.type)},
@@ -242,13 +238,46 @@ export class InstallPackageTool implements LanguageModelTool<IInstallPackageInpu
242238
options: LanguageModelToolInvocationPrepareOptions<IInstallPackageInput>,
243239
_token: CancellationToken,
244240
): Promise<PreparedToolInvocation> {
245-
const packageList = options.input.packageList || [];
246-
const packageCount = packageList.length;
247-
const packageText = packageCount === 1 ? 'package' : 'packages';
248-
const message = `Preparing to install Python ${packageText}: ${packageList.join(', ')}...`;
241+
const workspacePath = options.input.resourcePath ? Uri.file(options.input.resourcePath) : undefined;
242+
243+
const packageCount = options.input.packageList.length;
244+
let envName = '';
245+
try {
246+
const environment = await this.api.getEnvironment(workspacePath);
247+
envName = environment?.displayName || '';
248+
} catch {
249+
//
250+
}
251+
252+
let title = '';
253+
let invocationMessage = '';
254+
const message =
255+
packageCount === 1
256+
? ''
257+
: l10n.t(`The following packages will be installed: {0}`, options.input.packageList.sort().join(', '));
258+
if (envName) {
259+
title =
260+
packageCount === 1
261+
? l10n.t(`Install {0} in {1}?`, options.input.packageList[0], envName)
262+
: l10n.t(`Install packages in {0}?`, envName);
263+
invocationMessage =
264+
packageCount === 1
265+
? l10n.t(`Installing {0} in {1}`, options.input.packageList[0], envName)
266+
: l10n.t(`Installing packages {0} in {1}`, options.input.packageList.sort().join(', '), envName);
267+
} else {
268+
title =
269+
options.input.packageList.length === 1
270+
? l10n.t(`Install Python package '{0}'?`, options.input.packageList[0])
271+
: l10n.t(`Install Python packages?`);
272+
invocationMessage =
273+
packageCount === 1
274+
? l10n.t(`Installing Python package '{0}'`, options.input.packageList[0])
275+
: l10n.t(`Installing Python packages: {0}`, options.input.packageList.sort().join(', '));
276+
}
249277

250278
return {
251-
invocationMessage: message,
279+
confirmationMessages: { title, message },
280+
invocationMessage,
252281
};
253282
}
254283
}

src/features/creators/autoFindProjects.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ export class AutoFindProjects implements PythonProjectCreator {
5454
public readonly displayName = ProjectCreatorString.autoFindProjects;
5555
public readonly description = ProjectCreatorString.autoFindProjectsDescription;
5656

57+
supportsQuickCreate = true;
58+
5759
constructor(private readonly pm: PythonProjectManager) {}
5860

5961
async create(_options?: PythonProjectCreatorOptions): Promise<PythonProject | PythonProject[] | undefined> {
60-
const files = await findFiles('**/{pyproject.toml,setup.py}');
62+
const files = await findFiles('**/{pyproject.toml,setup.py}', '**/.venv/**');
6163
if (!files || files.length === 0) {
6264
setImmediate(() => {
6365
showErrorMessage('No projects found');

src/features/projectManager.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,21 @@ export class PythonProjectManagerImpl implements PythonProjectManager {
6767

6868
// For each override, resolve its path and add as a project if not already present
6969
for (const o of overrides) {
70-
const uri = Uri.file(path.resolve(w.uri.fsPath, o.path));
70+
let uriFromWorkspace: Uri | undefined = undefined;
71+
// if override has a workspace property, resolve the path relative to that workspace
72+
if (o.workspace) {
73+
//
74+
const workspaceFolder = workspaces.find((ws) => ws.name === o.workspace);
75+
if (workspaceFolder) {
76+
if (workspaceFolder.uri.toString() !== w.uri.toString()) {
77+
continue; // skip if the workspace is not the same as the current workspace
78+
}
79+
uriFromWorkspace = Uri.file(path.resolve(workspaceFolder.uri.fsPath, o.path));
80+
}
81+
}
82+
const uri = uriFromWorkspace ? uriFromWorkspace : Uri.file(path.resolve(w.uri.fsPath, o.path));
83+
84+
// Check if the project already exists in the newProjects array
7185
if (!newProjects.some((p) => p.uri.toString() === uri.toString())) {
7286
newProjects.push(new PythonProjectsImpl(o.path, uri));
7387
}

src/features/settings/settingHelpers.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { PythonProject } from '../../api';
1111
import { DEFAULT_ENV_MANAGER_ID, DEFAULT_PACKAGE_MANAGER_ID } from '../../common/constants';
1212
import { traceError, traceInfo } from '../../common/logging';
13-
import { getWorkspaceFile } from '../../common/workspace.apis';
13+
import { getWorkspaceFile, getWorkspaceFolders } from '../../common/workspace.apis';
1414
import { PythonProjectManager, PythonProjectSettings } from '../../internal.api';
1515

1616
function getSettings(
@@ -284,6 +284,7 @@ export interface EditProjectSettings {
284284
project: PythonProject;
285285
envManager?: string;
286286
packageManager?: string;
287+
workspace?: string;
287288
}
288289

289290
export async function addPythonProjectSetting(edits: EditProjectSettings[]): Promise<void> {
@@ -306,13 +307,23 @@ export async function addPythonProjectSetting(edits: EditProjectSettings[]): Pro
306307
traceError(`Unable to find workspace for ${e.project.uri.fsPath}`);
307308
});
308309

310+
const isMultiroot = (getWorkspaceFolders() ?? []).length > 1;
311+
309312
const promises: Thenable<void>[] = [];
310313
workspaces.forEach((es, w) => {
311314
const config = workspace.getConfiguration('python-envs', w.uri);
312315
const overrides = config.get<PythonProjectSettings[]>('pythonProjects', []);
313316
es.forEach((e) => {
317+
if (isMultiroot) {
318+
}
314319
const pwPath = path.normalize(e.project.uri.fsPath);
315-
const index = overrides.findIndex((s) => path.resolve(w.uri.fsPath, s.path) === pwPath);
320+
const index = overrides.findIndex((s) => {
321+
if (s.workspace) {
322+
// If the workspace is set, check workspace and path in existing overrides
323+
return s.workspace === w.name && path.resolve(w.uri.fsPath, s.path) === pwPath;
324+
}
325+
return path.resolve(w.uri.fsPath, s.path) === pwPath;
326+
});
316327
if (index >= 0) {
317328
overrides[index].envManager = e.envManager ?? envManager;
318329
overrides[index].packageManager = e.packageManager ?? pkgManager;
@@ -321,6 +332,7 @@ export async function addPythonProjectSetting(edits: EditProjectSettings[]): Pro
321332
path: path.relative(w.uri.fsPath, pwPath).replace(/\\/g, '/'),
322333
envManager,
323334
packageManager: pkgManager,
335+
workspace: isMultiroot ? w.name : undefined,
324336
});
325337
}
326338
});

src/internal.api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ export interface PythonProjectSettings {
295295
path: string;
296296
envManager: string;
297297
packageManager: string;
298+
workspace?: string;
298299
}
299300

300301
export class PythonEnvironmentImpl implements PythonEnvironment {

src/managers/builtin/venvUtils.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
NativePythonEnvironmentKind,
3333
NativePythonFinder,
3434
} from '../common/nativePythonFinder';
35-
import { shortVersion, sortEnvironments } from '../common/utils';
35+
import { pathForGitBash, shortVersion, sortEnvironments } from '../common/utils';
3636
import { isUvInstalled, runPython, runUV } from './helpers';
3737
import { getProjectInstallable, getWorkspacePackagesToInstall, PipPackages } from './pipUtils';
3838
import { resolveSystemPythonEnvironmentPath } from './utils';
@@ -113,10 +113,6 @@ function getName(binPath: string): string {
113113
return path.basename(dir1);
114114
}
115115

116-
function pathForGitBash(binPath: string): string {
117-
return isWindows() ? binPath.replace(/\\/g, '/').replace(/^([a-zA-Z]):/, '/$1') : binPath;
118-
}
119-
120116
async function getPythonInfo(env: NativeEnvInfo): Promise<PythonEnvironmentInfo> {
121117
if (env.executable && env.version && env.prefix) {
122118
const venvName = env.name ?? getName(env.executable);

0 commit comments

Comments
 (0)