Skip to content

New package Flow #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: create-project-branch
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@
"category": "Python Envs",
"icon": "$(terminal)"
},
{
"command": "python-envs.createNewProjectFromTemplate",
"title": "%python-envs.createNewProjectFromTemplate.title%",
"category": "Python Envs",
"icon": "$(play)"
},
{
"command": "python-envs.runAsTask",
"title": "%python-envs.runAsTask.title%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"python-envs.runInTerminal.title": "Run in Terminal",
"python-envs.createTerminal.title": "Create Python Terminal",
"python-envs.runAsTask.title": "Run as Task",
"python-envs.createNewProjectFromTemplate.title": "Create New Project from Template",
"python-envs.terminal.activate.title": "Activate Environment in Current Terminal",
"python-envs.terminal.deactivate.title": "Deactivate Environment in Current Terminal",
"python-envs.uninstallPackage.title": "Uninstall Package"
Expand Down
14 changes: 7 additions & 7 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// Licensed under the MIT License.

import {
Uri,
Disposable,
MarkdownString,
Event,
FileChangeType,
LogOutputChannel,
ThemeIcon,
Terminal,
MarkdownString,
TaskExecution,
Terminal,
TerminalOptions,
FileChangeType,
ThemeIcon,
Uri,
} from 'vscode';

/**
Expand Down Expand Up @@ -333,7 +333,7 @@ export interface QuickCreateConfig {
*/
export interface EnvironmentManager {
/**
* The name of the environment manager.
* The name of the environment manager. Allowed characters (a-z, A-Z, 0-9, -, _).
*/
readonly name: string;

Expand Down Expand Up @@ -564,7 +564,7 @@ export interface DidChangePackagesEventArgs {
*/
export interface PackageManager {
/**
* The name of the package manager.
* The name of the package manager. Allowed characters (a-z, A-Z, 0-9, -, _).
*/
name: string;

Expand Down
2 changes: 2 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export const KNOWN_FILES = [
];

export const KNOWN_TEMPLATE_ENDINGS = ['.j2', '.jinja2'];

export const NEW_PROJECT_TEMPLATES_FOLDER = path.join(EXTENSION_ROOT_DIR, 'src', 'features', 'creators', 'templates');
7 changes: 6 additions & 1 deletion src/common/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export namespace Common {
export const installPython = l10n.t('Install Python');
}

export namespace WorkbenchStrings {
export const installExtension = l10n.t('Install Extension');
}

export namespace Interpreter {
export const statusBarSelect = l10n.t('Select Interpreter');
export const browsePath = l10n.t('Browse...');
Expand Down Expand Up @@ -165,10 +169,11 @@ export namespace EnvViewStrings {
export const selectedWorkspaceTooltip = l10n.t('This environment is selected for workspace files');
}

export namespace ShellStartupActivationStrings {
export namespace ActivationStrings {
export const envCollectionDescription = l10n.t('Environment variables for shell activation');
export const revertedShellStartupScripts = l10n.t(
'Removed shell startup profile code for Python environment activation. See [logs](command:{0})',
Commands.viewLogs,
);
export const activatingEnvironment = l10n.t('Activating environment');
}
90 changes: 82 additions & 8 deletions src/common/pickers/managers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { QuickPickItem, QuickPickItemKind } from 'vscode';
import { commands, QuickInputButtons, QuickPickItem, QuickPickItemKind } from 'vscode';
import { PythonProjectCreator } from '../../api';
import { InternalEnvironmentManager, InternalPackageManager } from '../../internal.api';
import { Common, Pickers } from '../localize';
import { showQuickPickWithButtons, showQuickPick } from '../window.apis';
import { showQuickPickWithButtons } from '../window.apis';

function getDescription(mgr: InternalEnvironmentManager | InternalPackageManager): string | undefined {
if (mgr.description) {
Expand Down Expand Up @@ -133,14 +133,88 @@ export async function pickCreator(creators: PythonProjectCreator[]): Promise<Pyt
return creators[0];
}

const items: (QuickPickItem & { c: PythonProjectCreator })[] = creators.map((c) => ({
// First level menu
const autoFindCreator = creators.find((c) => c.name === 'autoProjects');
const existingProjectsCreator = creators.find((c) => c.name === 'existingProjects');

const items: QuickPickItem[] = [
{
label: 'Auto Find',
description: autoFindCreator?.description ?? 'Automatically find Python projects',
},
{
label: 'Select Existing',
description: existingProjectsCreator?.description ?? 'Select existing Python projects',
},
{
label: 'Create New',
description: 'Create a Python project from a template',
},
];

const selected = await showQuickPickWithButtons(items, {
placeHolder: Pickers.Managers.selectProjectCreator,
ignoreFocusOut: true,
});

if (!selected) {
return undefined;
}

// Return appropriate creator based on selection
// Handle case where selected could be an array (should not happen, but for type safety)
const selectedItem = Array.isArray(selected) ? selected[0] : selected;
if (!selectedItem) {
return undefined;
}
switch (selectedItem.label) {
case 'Auto Find':
return autoFindCreator;
case 'Select Existing':
return existingProjectsCreator;
case 'Create New':
return newProjectSelection(creators);
}

return undefined;
}

export async function newProjectSelection(creators: PythonProjectCreator[]): Promise<PythonProjectCreator | undefined> {
const otherCreators = creators.filter((c) => c.name !== 'autoProjects' && c.name !== 'existingProjects');

// Show second level menu for other creators
if (otherCreators.length === 0) {
return undefined;
}
const newItems: (QuickPickItem & { c: PythonProjectCreator })[] = otherCreators.map((c) => ({
label: c.displayName ?? c.name,
description: c.description,
c: c,
}));
const selected = await showQuickPick(items, {
placeHolder: Pickers.Managers.selectProjectCreator,
ignoreFocusOut: true,
});
return (selected as { c: PythonProjectCreator })?.c;
try {
const newSelected = await showQuickPickWithButtons(newItems, {
placeHolder: 'Select project type for new project',
ignoreFocusOut: true,
showBackButton: true,
});

if (!newSelected) {
// User cancelled the picker
return undefined;
}
// Handle back button
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((newSelected as any)?.kind === -1 || (newSelected as any)?.back === true) {
// User pressed the back button, re-show the first menu
return pickCreator(creators);
}

// Handle case where newSelected could be an array (should not happen, but for type safety)
const selectedCreator = Array.isArray(newSelected) ? newSelected[0] : newSelected;
return selectedCreator?.c;
} catch (ex) {
if (ex === QuickInputButtons.Back) {
await commands.executeCommand('python-envs.addPythonProject');
}
}
}
12 changes: 12 additions & 0 deletions src/common/workbenchCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { commands, Uri } from 'vscode';

export async function installExtension(
extensionId: Uri | string,
options?: {
installOnlyNewlyAddedFromExtensionPackVSIX?: boolean;
installPreReleaseVersion?: boolean;
donotSync?: boolean;
},
): Promise<void> {
await commands.executeCommand('workbench.extensions.installExtension', extensionId, options);
}
18 changes: 15 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { commands, ExtensionContext, LogOutputChannel, Terminal, Uri } from 'vscode';

import { PythonEnvironment, PythonEnvironmentApi } from './api';
import { ensureCorrectVersion } from './common/extVersion';
import { registerTools } from './common/lm.apis';
import { registerLogger, traceError, traceInfo } from './common/logging';
import { setPersistentState } from './common/persistentState';
import { newProjectSelection } from './common/pickers/managers';
import { StopWatch } from './common/stopWatch';
import { EventNames } from './common/telemetry/constants';
import { sendManagerSelectionTelemetry } from './common/telemetry/helpers';
Expand All @@ -16,12 +16,15 @@ import {
onDidChangeActiveTextEditor,
onDidChangeTerminalShellIntegration,
} from './common/window.apis';
import { createManagerReady } from './features/common/managerReady';
import { GetEnvironmentInfoTool, InstallPackageTool } from './features/copilotTools';
import { AutoFindProjects } from './features/creators/autoFindProjects';
import { ExistingProjects } from './features/creators/existingProjects';
import { NewPackageProject } from './features/creators/newPackageProject';
import { NewScriptProject } from './features/creators/newScriptProject';
import { ProjectCreatorsImpl } from './features/creators/projectCreators';
import {
addPythonProject,
addPythonProjectCommand,
copyPathToClipboard,
createAnyEnvironmentCommand,
createEnvironmentCommand,
Expand Down Expand Up @@ -88,6 +91,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
context.subscriptions.push(envVarManager);

const envManagers: EnvironmentManagers = new PythonEnvironmentManagers(projectManager);
createManagerReady(envManagers, projectManager, context.subscriptions);
context.subscriptions.push(envManagers);

const terminalActivation = new TerminalActivationImpl();
Expand All @@ -110,6 +114,8 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
projectCreators,
projectCreators.registerPythonProjectCreator(new ExistingProjects(projectManager)),
projectCreators.registerPythonProjectCreator(new AutoFindProjects(projectManager)),
projectCreators.registerPythonProjectCreator(new NewPackageProject(envManagers, projectManager)),
projectCreators.registerPythonProjectCreator(new NewScriptProject()),
);

setPythonApi(envManagers, projectManager, projectCreators, terminalManager, envVarManager);
Expand Down Expand Up @@ -185,7 +191,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
await setPackageManagerCommand(envManagers, projectManager);
}),
commands.registerCommand('python-envs.addPythonProject', async (resource) => {
await addPythonProject(resource, projectManager, envManagers, projectCreators);
await addPythonProjectCommand(resource, projectManager, envManagers, projectCreators);
}),
commands.registerCommand('python-envs.removePythonProject', async (item) => {
await resetEnvironmentCommand(item, envManagers, projectManager);
Expand Down Expand Up @@ -228,6 +234,12 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
await terminalManager.deactivate(terminal);
}
}),
commands.registerCommand('python-envs.createNewProjectFromTemplate', async () => {
const selected = await newProjectSelection(projectCreators.getProjectCreators());
if (selected) {
await selected.create();
}
}),
terminalActivation.onDidChangeTerminalActivationState(async (e) => {
await setActivateMenuButtonContext(e.terminal, e.environment, e.activated);
}),
Expand Down
Loading