diff --git a/src/api.ts b/src/api.ts index 53790aa1..f2258ccd 100644 --- a/src/api.ts +++ b/src/api.ts @@ -379,7 +379,7 @@ export interface EnvironmentManager { quickCreateConfig?(): QuickCreateConfig | undefined; /** - * Creates a new Python environment within the specified scope. + * Creates a new Python environment within the specified scope. Create should support adding a .gitignore file if it creates a folder within the workspace. * @param scope - The scope within which to create the environment. * @param options - Optional parameters for creating the Python environment. * @returns A promise that resolves to the created Python environment, or undefined if creation failed. diff --git a/src/common/telemetry/constants.ts b/src/common/telemetry/constants.ts index 432f7dfd..14a653da 100644 --- a/src/common/telemetry/constants.ts +++ b/src/common/telemetry/constants.ts @@ -77,7 +77,7 @@ export interface IEventNamePropertyMapping { }; /* __GDPR__ - "package.install": { + "package_management": { "managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, "result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } } diff --git a/src/extension.ts b/src/extension.ts index 7386d97e..56fcb0e7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,4 +1,4 @@ -import { commands, extensions, ExtensionContext, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode'; +import { commands, ExtensionContext, extensions, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode'; import { PythonEnvironment, PythonEnvironmentApi, PythonProjectCreator } from './api'; import { ensureCorrectVersion } from './common/extVersion'; import { registerLogger, traceError, traceInfo } from './common/logging'; @@ -8,6 +8,7 @@ import { StopWatch } from './common/stopWatch'; import { EventNames } from './common/telemetry/constants'; import { sendManagerSelectionTelemetry } from './common/telemetry/helpers'; import { sendTelemetryEvent } from './common/telemetry/sender'; +import { createSimpleDebounce } from './common/utils/debounce'; import { createDeferred } from './common/utils/deferred'; import { activeTerminal, @@ -75,27 +76,27 @@ import { registerPyenvFeatures } from './managers/pyenv/main'; async function collectEnvironmentInfo( context: ExtensionContext, envManagers: EnvironmentManagers, - projectManager: PythonProjectManager + projectManager: PythonProjectManager, ): Promise { const info: string[] = []; - + try { // Extension version const extensionVersion = context.extension?.packageJSON?.version || 'unknown'; info.push(`Extension Version: ${extensionVersion}`); - + // Python extension version const pythonExtension = extensions.getExtension('ms-python.python'); const pythonVersion = pythonExtension?.packageJSON?.version || 'not installed'; info.push(`Python Extension Version: ${pythonVersion}`); - + // Environment managers const managers = envManagers.managers; info.push(`\nRegistered Environment Managers (${managers.length}):`); - managers.forEach(manager => { + managers.forEach((manager) => { info.push(` - ${manager.id} (${manager.displayName})`); }); - + // Available environments const allEnvironments: PythonEnvironment[] = []; for (const manager of managers) { @@ -106,7 +107,7 @@ async function collectEnvironmentInfo( info.push(` Error getting environments from ${manager.id}: ${err}`); } } - + info.push(`\nTotal Available Environments: ${allEnvironments.length}`); if (allEnvironments.length > 0) { info.push('Environment Details:'); @@ -117,8 +118,9 @@ async function collectEnvironmentInfo( info.push(` ... and ${allEnvironments.length - 10} more environments`); } } - + // Python projects + console.log('getProjects called from extension.ts activate'); const projects = projectManager.getProjects(); info.push(`\nPython Projects (${projects.length}):`); for (let index = 0; index < projects.length; index++) { @@ -133,22 +135,25 @@ async function collectEnvironmentInfo( info.push(` Error getting environment: ${err}`); } } - + // Current settings (non-sensitive) const config = workspace.getConfiguration('python-envs'); info.push('\nExtension Settings:'); info.push(` Default Environment Manager: ${config.get('defaultEnvManager')}`); info.push(` Default Package Manager: ${config.get('defaultPackageManager')}`); info.push(` Terminal Auto Activation: ${config.get('terminal.autoActivationType')}`); - } catch (err) { info.push(`\nError collecting environment information: ${err}`); } - + return info.join('\n'); } export async function activate(context: ExtensionContext): Promise { + // Debounced version of updateViewsAndStatus to avoid excessive UI updates + const debouncedUpdateViewsAndStatus = createSimpleDebounce(200, () => { + updateViewsAndStatus(statusBar, workspaceView, managerView, api); + }); const start = new StopWatch(); // Logging should be set up before anything else. @@ -366,11 +371,11 @@ export async function activate(context: ExtensionContext): Promise { try { const issueData = await collectEnvironmentInfo(context, envManagers, projectManager); - + await commands.executeCommand('workbench.action.openIssueReporter', { extensionId: 'ms-python.vscode-python-envs', issueTitle: '[Python Environments] ', - issueBody: `\n\n\n\n
\nEnvironment Information\n\n\`\`\`\n${issueData}\n\`\`\`\n\n
` + issueBody: `\n\n\n\n
\nEnvironment Information\n\n\`\`\`\n${issueData}\n\`\`\`\n\n
`, }); } catch (error) { window.showErrorMessage(`Failed to open issue reporter: ${error}`); @@ -387,22 +392,16 @@ export async function activate(context: ExtensionContext): Promise { - updateViewsAndStatus(statusBar, workspaceView, managerView, api); - }), - envManagers.onDidChangeEnvironment(async () => { - updateViewsAndStatus(statusBar, workspaceView, managerView, api); + window.onDidChangeActiveTextEditor(() => { + debouncedUpdateViewsAndStatus.trigger(); }), - envManagers.onDidChangeEnvironments(async () => { - updateViewsAndStatus(statusBar, workspaceView, managerView, api); - }), - envManagers.onDidChangeEnvironmentFiltered(async (e) => { + envManagers.onDidChangeEnvironmentFiltered((e) => { managerView.environmentChanged(e); const location = e.uri?.fsPath ?? 'global'; traceInfo( `Internal: Changed environment from ${e.old?.displayName} to ${e.new?.displayName} for: ${location}`, ); - updateViewsAndStatus(statusBar, workspaceView, managerView, api); + debouncedUpdateViewsAndStatus.trigger(); }), onDidChangeTerminalShellIntegration(async (e) => { const shellEnv = e.shellIntegration?.env; @@ -428,6 +427,16 @@ export async function activate(context: ExtensionContext): Promise { + debouncedUpdateViewsAndStatus.trigger(); + }), + envManagers.onDidChangeEnvironments(() => { + debouncedUpdateViewsAndStatus.trigger(); + }), + ); + /** * Below are all the contributed features using the APIs. */ diff --git a/src/features/creators/newPackageProject.ts b/src/features/creators/newPackageProject.ts index 57bfe84d..63dcae73 100644 --- a/src/features/creators/newPackageProject.ts +++ b/src/features/creators/newPackageProject.ts @@ -117,7 +117,7 @@ export class NewPackageProject implements PythonProjectCreator { uri: Uri.file(projectDestinationFolder), }; // add package to list of packages - this.projectManager.add(createdPackage); + await this.projectManager.add(createdPackage); // 4. Create virtual environment if requested let createdEnv: PythonEnvironment | undefined; diff --git a/src/features/projectManager.ts b/src/features/projectManager.ts index 526d9a20..733278a9 100644 --- a/src/features/projectManager.ts +++ b/src/features/projectManager.ts @@ -171,7 +171,6 @@ export class PythonProjectManagerImpl implements PythonProjectManager { } getProjects(uris?: Uri[]): ReadonlyArray { - console.log('getProjects', uris); if (uris === undefined) { return Array.from(this._projects.values()); } else { diff --git a/src/managers/builtin/venvManager.ts b/src/managers/builtin/venvManager.ts index 122d5e8c..c1464d87 100644 --- a/src/managers/builtin/venvManager.ts +++ b/src/managers/builtin/venvManager.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs/promises'; import * as path from 'path'; import { EventEmitter, l10n, LogOutputChannel, MarkdownString, ProgressLocation, ThemeIcon, Uri } from 'vscode'; import { @@ -20,6 +21,7 @@ import { } from '../../api'; import { PYTHON_EXTENSION_ID } from '../../common/constants'; import { VenvManagerStrings } from '../../common/localize'; +import { traceError } from '../../common/logging'; import { createDeferred, Deferred } from '../../common/utils/deferred'; import { showErrorMessage, withProgress } from '../../common/window.apis'; import { findParentIfFile } from '../../features/envCommands'; @@ -162,6 +164,17 @@ export class VenvManager implements EnvironmentManager { } if (environment) { this.addEnvironment(environment, true); + + // Add .gitignore to the .venv folder + try { + const venvDir = environment.environmentPath.fsPath; + const gitignorePath = path.join(venvDir, '.gitignore'); + await fs.writeFile(gitignorePath, '*\n', { flag: 'w' }); + } catch (err) { + traceError( + `Failed to create .gitignore in venv: ${err instanceof Error ? err.message : String(err)}`, + ); + } } return environment; } finally { diff --git a/src/managers/conda/condaEnvManager.ts b/src/managers/conda/condaEnvManager.ts index 229ad4b1..c044afce 100644 --- a/src/managers/conda/condaEnvManager.ts +++ b/src/managers/conda/condaEnvManager.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs-extra'; import * as path from 'path'; import { Disposable, EventEmitter, l10n, LogOutputChannel, MarkdownString, ProgressLocation, Uri } from 'vscode'; import { @@ -19,6 +20,7 @@ import { SetEnvironmentScope, } from '../../api'; import { CondaStrings } from '../../common/localize'; +import { traceError } from '../../common/logging'; import { createDeferred, Deferred } from '../../common/utils/deferred'; import { showErrorMessage, withProgress } from '../../common/window.apis'; import { NativePythonFinder } from '../common/nativePythonFinder'; @@ -167,6 +169,20 @@ export class CondaEnvManager implements EnvironmentManager, Disposable { } if (result) { this.addEnvironment(result); + + // If the environment is inside the workspace, add a .gitignore file + try { + const projectUris = this.api.getPythonProjects().map((p) => p.uri.fsPath); + const envPath = result.environmentPath?.fsPath; + if (envPath && projectUris.some((root) => envPath.startsWith(root))) { + const gitignorePath = path.join(envPath, '.gitignore'); + await fs.writeFile(gitignorePath, '*\n', { flag: 'w' }); + } + } catch (err) { + traceError( + `Failed to create .gitignore in conda env: ${err instanceof Error ? err.message : String(err)}`, + ); + } } return result;