Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/common/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,15 @@ export namespace CondaStrings {
export const condaRemoveFailed = l10n.t('Failed to remove conda environment');
export const condaExists = l10n.t('Environment already exists');
}

export namespace ProjectCreatorString {
export const addExistingProjects = l10n.t('Add Existing Projects');
export const autoFindProjects = l10n.t('Auto Find Projects');
export const selectProjects = l10n.t('Select Python projects');
export const selectFilesOrFolders = l10n.t('Select Project folders or Python files');
export const autoFindProjectsDescription = l10n.t(
'Automatically find folders with `pyproject.toml` or `setup.py` files.',
);

export const noProjectsFound = l10n.t('No projects found');
}
3 changes: 3 additions & 0 deletions src/common/utils/asyncUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function sleep(milliseconds: number) {
return new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
}
12 changes: 5 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ import { getPythonApi, setPythonApi } from './features/pythonApi';
import { setPersistentState } from './common/persistentState';
import { createNativePythonFinder, NativePythonFinder } from './managers/common/nativePythonFinder';
import { PythonEnvironmentApi } from './api';
import {
ProjectCreatorsImpl,
registerAutoProjectProvider,
registerExistingProjectProvider,
} from './features/projectCreators';
import { ProjectCreatorsImpl } from './features/creators/projectCreators';
import { ProjectView } from './features/views/projectView';
import { registerCompletionProvider } from './features/settings/settingCompletions';
import { TerminalManager, TerminalManagerImpl } from './features/terminal/terminalManager';
Expand All @@ -56,6 +52,8 @@ import { StopWatch } from './common/stopWatch';
import { sendTelemetryEvent } from './common/telemetry/sender';
import { EventNames } from './common/telemetry/constants';
import { ensureCorrectVersion } from './common/extVersion';
import { ExistingProjects } from './features/creators/existingProjects';
import { AutoFindProjects } from './features/creators/autoFindProjects';

export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi> {
const start = new StopWatch();
Expand Down Expand Up @@ -87,8 +85,8 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
const projectCreators: ProjectCreators = new ProjectCreatorsImpl();
context.subscriptions.push(
projectCreators,
registerExistingProjectProvider(projectCreators),
registerAutoProjectProvider(projectCreators),
projectCreators.registerPythonProjectCreator(new ExistingProjects()),
projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager)),
);

setPythonApi(envManagers, projectManager, projectCreators, terminalManager, envVarManager);
Expand Down
95 changes: 95 additions & 0 deletions src/features/creators/autoFindProjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as path from 'path';
import { Uri } from 'vscode';
import { showQuickPickWithButtons } from '../../common/window.apis';
import { ProjectCreatorString } from '../../common/localize';
import { PythonProject, PythonProjectCreator, PythonProjectCreatorOptions } from '../../api';
import { PythonProjectManager } from '../../internal.api';
import { showErrorMessage } from '../../common/errors/utils';
import { findFiles } from '../../common/workspace.apis';

function getUniqueUri(uris: Uri[]): {
label: string;
description: string;
uri: Uri;
}[] {
const files = uris.map((uri) => uri.fsPath).sort();
const dirs: Map<string, string> = new Map();
files.forEach((file) => {
const dir = path.dirname(file);
if (dirs.has(dir)) {
return;
}
dirs.set(dir, file);
});
return Array.from(dirs.entries())
.map(([dir, file]) => ({
label: path.basename(dir),
description: file,
uri: Uri.file(dir),
}))
.sort((a, b) => a.label.localeCompare(b.label));
}

async function pickProjects(uris: Uri[]): Promise<Uri[] | undefined> {
const items = getUniqueUri(uris);

const selected = await showQuickPickWithButtons(items, {
canPickMany: true,
ignoreFocusOut: true,
placeHolder: ProjectCreatorString.selectProjects,
showBackButton: true,
});

if (Array.isArray(selected)) {
return selected.map((s) => s.uri);
} else if (selected) {
return [selected.uri];
}

return undefined;
}

export class AutoFindProjects implements PythonProjectCreator {
public readonly name = 'autoProjects';
public readonly displayName = ProjectCreatorString.autoFindProjects;
public readonly description = ProjectCreatorString.autoFindProjectsDescription;

constructor(private readonly pm: PythonProjectManager) {}

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

const filtered = files.filter((uri) => {
const p = this.pm.get(uri);
if (p) {
// If there ia already a project with the same path, skip it.
// If there is a project with the same parent path, skip it.
const np = path.normalize(p.uri.fsPath);
const nf = path.normalize(uri.fsPath);
const nfp = path.dirname(nf);
return np !== nf && np !== nfp;
}
return true;
});

if (filtered.length === 0) {
return;
}

const projects = await pickProjects(filtered);
if (!projects || projects.length === 0) {
return;
}

return projects.map((uri) => ({
name: path.basename(uri.fsPath),
uri,
}));
}
}
30 changes: 30 additions & 0 deletions src/features/creators/existingProjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as path from 'path';
import { PythonProject, PythonProjectCreator, PythonProjectCreatorOptions } from '../../api';
import { ProjectCreatorString } from '../../common/localize';
import { showOpenDialog } from '../../common/window.apis';

export class ExistingProjects implements PythonProjectCreator {
public readonly name = 'existingProjects';
public readonly displayName = ProjectCreatorString.addExistingProjects;

async create(_options?: PythonProjectCreatorOptions): Promise<PythonProject | PythonProject[] | undefined> {
const results = await showOpenDialog({
canSelectFiles: true,
canSelectFolders: true,
canSelectMany: true,
filters: {
python: ['py'],
},
title: ProjectCreatorString.selectFilesOrFolders,
});

if (!results || results.length === 0) {
return;
}

return results.map((r) => ({
name: path.basename(r.fsPath),
uri: r,
}));
}
}
21 changes: 21 additions & 0 deletions src/features/creators/projectCreators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Disposable } from 'vscode';
import { PythonProjectCreator } from '../../api';
import { ProjectCreators } from '../../internal.api';

export class ProjectCreatorsImpl implements ProjectCreators {
private _creators: PythonProjectCreator[] = [];

registerPythonProjectCreator(creator: PythonProjectCreator): Disposable {
this._creators.push(creator);
return new Disposable(() => {
this._creators = this._creators.filter((item) => item !== creator);
});
}
getProjectCreators(): PythonProjectCreator[] {
return this._creators;
}

dispose() {
this._creators = [];
}
}
123 changes: 0 additions & 123 deletions src/features/projectCreators.ts

This file was deleted.

Loading
Loading