Skip to content

Commit 664656d

Browse files
Copilotedvilme
andauthored
Auto Find: exclude markers inside each workspace folder's selected environment sysPrefix (#1843)
Fixes #406 Auto Find searches for `pyproject.toml` and `setup.py` but excludes only `**/.venv/**`. As a result, packages installed in a selected environment with any other directory name (for example `custom-env/lib/.../site-packages/pkg/setup.py`) are offered as projects. This PR drops those false positives using the `sysPrefix` of the environment selected for each workspace folder. ### Changes - **`AutoFindProjects`** (`src/features/creators/autoFindProjects.ts`) - Takes `EnvironmentManagers` as a new constructor dependency. - After `findFiles` returns results, calls `envManagers.getEnvironment(folder.uri)` once for each folder in `getWorkspaceFolders()`. Only non-empty, absolute `sysPrefix` values are kept. - Drops markers at or below any of those prefixes using the existing `isSameOrParentPath`, which already enforces path boundaries and handles Windows case and separators. - The filter runs **before** the "No projects found" and "already registered" checks. If every match is inside an environment, the user sees "No projects found", not a misleading "already exists as project" warning. - A lookup failure is logged with `traceWarn` and that folder is skipped; discovery continues. - **Wiring** (`src/extension.ts`): passes the existing `envManagers` instance to `AutoFindProjects`. ```ts const found = await findFiles('**/{pyproject.toml,setup.py}', '**/.venv/**'); const prefixes = found && found.length > 0 ? await this.getSelectedEnvironmentPrefixes() : []; const files = found?.filter((uri) => !prefixes.some((prefix) => isSameOrParentPath(prefix, uri.fsPath))); ``` ### Unchanged - `**/.venv/**` exclusion, picker, de-duplication and registration. - With no selected environment or no usable prefix, behaviour is the same as before. ### Out of scope - Inactive, unselected or global environments. - Environments selected for nested projects. - Symlinked environment paths. - Environment refreshes, caches or watchers. - Settings or API changes. - **Add Existing**. An environment that is not the selected one for any workspace folder is still excluded only if it is named `.venv`. ### Tests (`autoFindProjects.unit.test.ts`) - Constructor mocks updated. - A `custom-env` selected environment excludes both marker types, including a marker at the environment root. A real project next to it and a similarly named sibling `custom-env-project` are still offered. - With two workspace folders, each folder's prefix is applied separately (one lookup per folder). - An `undefined` prefix, a relative prefix and a thrown lookup each leave existing behaviour unchanged. - When every marker is inside an environment, "No projects found" is shown with no picker, warning or registration. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com>
1 parent 3b85460 commit 664656d

3 files changed

Lines changed: 266 additions & 17 deletions

File tree

‎src/extension.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
220220
context.subscriptions.push(
221221
projectCreators,
222222
projectCreators.registerPythonProjectCreator(new ExistingProjects(projectManager)),
223-
projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager)),
223+
projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager, envManagers)),
224224
projectCreators.registerPythonProjectCreator(new NewPackageProject(envManagers, projectManager)),
225225
projectCreators.registerPythonProjectCreator(new NewScriptProject(projectManager)),
226226
);

‎src/features/creators/autoFindProjects.ts‎

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import * as path from 'path';
2-
import { Uri } from 'vscode';
2+
import { Uri, WorkspaceFolder } from 'vscode';
33
import { PythonProject, PythonProjectCreator, PythonProjectCreatorOptions } from '../../api';
44
import { ProjectCreatorString } from '../../common/localize';
5-
import { traceInfo } from '../../common/logging';
5+
import { traceInfo, traceWarn } from '../../common/logging';
66
import { showErrorMessage, showQuickPickWithButtons, showWarningMessage } from '../../common/window.apis';
7-
import { findFiles } from '../../common/workspace.apis';
7+
import { findFiles, getWorkspaceFolders } from '../../common/workspace.apis';
8+
import type { EnvironmentManagers } from '../envManagers';
89
import {
910
PythonProjectManager,
1011
PythonProjectsImpl,
1112
} from '../projectManager';
12-
import { normalizePath } from '../../common/utils/pathUtils';
13+
import { isSameOrParentPath, normalizePath } from '../../common/utils/pathUtils';
1314

1415
function getUniqueUri(uris: Uri[]): {
1516
label: string;
@@ -60,10 +61,52 @@ export class AutoFindProjects implements PythonProjectCreator {
6061

6162
supportsQuickCreate = true;
6263

63-
constructor(private readonly pm: PythonProjectManager) {}
64+
constructor(
65+
private readonly pm: PythonProjectManager,
66+
private readonly envManagers: EnvironmentManagers,
67+
) {}
68+
69+
/**
70+
* Returns selected environment prefixes inside an open workspace folder.
71+
* Lookup failures are logged and skipped.
72+
*/
73+
private async getSelectedEnvironmentPrefixes(folders: readonly WorkspaceFolder[]): Promise<string[]> {
74+
const prefixes = await Promise.all(
75+
folders.map(async (folder) => {
76+
try {
77+
const environment = await this.envManagers.getEnvironment(folder.uri);
78+
const prefix = environment?.sysPrefix;
79+
return prefix &&
80+
path.isAbsolute(prefix) &&
81+
folders.some((workspaceFolder) => isSameOrParentPath(workspaceFolder.uri.fsPath, prefix))
82+
? prefix
83+
: undefined;
84+
} catch (ex) {
85+
traceWarn(`Auto Find: failed to get environment for ${folder.uri.fsPath}`, ex);
86+
return undefined;
87+
}
88+
}),
89+
);
90+
return prefixes.filter((prefix): prefix is string => !!prefix);
91+
}
6492

6593
async create(_options?: PythonProjectCreatorOptions): Promise<PythonProject | PythonProject[] | undefined> {
66-
const files = await findFiles('**/{pyproject.toml,setup.py}', '**/.venv/**');
94+
const found = await findFiles('**/{pyproject.toml,setup.py}', '**/.venv/**');
95+
// Exclude markers inside selected environments (e.g. installed packages in a custom-named venv).
96+
const folders = found && found.length > 0 ? getWorkspaceFolders() ?? [] : [];
97+
const prefixes = await this.getSelectedEnvironmentPrefixes(folders);
98+
const files = found?.filter(
99+
(uri) =>
100+
!prefixes.some(
101+
(prefix) =>
102+
isSameOrParentPath(prefix, uri.fsPath) &&
103+
!folders.some(
104+
(folder) =>
105+
isSameOrParentPath(prefix, folder.uri.fsPath) &&
106+
isSameOrParentPath(folder.uri.fsPath, uri.fsPath),
107+
),
108+
),
109+
);
67110
if (!files || files.length === 0) {
68111
setImmediate(() => {
69112
showErrorMessage('No projects found');

0 commit comments

Comments
 (0)