Skip to content

Commit 22c77f0

Browse files
committed
first draft
1 parent 704dae6 commit 22c77f0

File tree

6 files changed

+1763
-434
lines changed

6 files changed

+1763
-434
lines changed

src/extension/common/application/commands/reportIssueCommand.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import * as path from 'path';
88
import { executeCommand } from '../../vscodeapi';
99
import { getActiveEnvironmentPath, resolveEnvironment } from '../../python';
1010
import { EXTENSION_ROOT_DIR } from '../../constants';
11-
import { getRawVersion } from '../../settings';
1211
import { sendTelemetryEvent } from '../../../telemetry';
1312
import { EventName } from '../../../telemetry/constants';
1413

@@ -22,9 +21,9 @@ export async function openReportIssue(): Promise<void> {
2221
const userTemplate = await fs.readFile(userDataTemplatePath, 'utf8');
2322
const interpreterPath = await getActiveEnvironmentPath();
2423
const interpreter = await resolveEnvironment(interpreterPath);
25-
const virtualEnvKind = interpreter?.environment?.type || 'Unknown';
24+
const virtualEnvKind = interpreter?.envId.managerId ?? 'N/A';
2625

27-
const pythonVersion = getRawVersion(interpreter?.version);
26+
const pythonVersion = interpreter?.version ?? 'unknown';
2827
await executeCommand('workbench.action.openIssueReporter', {
2928
extensionId: 'ms-python.debugpy',
3029
issueBody: template,

src/extension/common/python.ts

Lines changed: 104 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,16 @@ import {
1111
ResolvedEnvironment,
1212
Resource,
1313
} from '@vscode/python-extension';
14-
import { commands, EventEmitter, extensions, Uri, Event, Disposable, Extension } from 'vscode';
15-
import { createDeferred } from './utils/async';
14+
import { commands, EventEmitter, extensions, Uri, Event, Disposable } from 'vscode';
1615
import { traceError, traceLog } from './log/logging';
16+
import { PythonEnvironment, PythonEnvironmentApi, PythonEnvsExtension } from '../envExtApi';
1717

18-
/**
19-
* Interface for the Python extension API.
20-
*/
21-
interface IExtensionApi {
22-
ready: Promise<void>;
23-
settings: {
24-
getExecutionDetails(resource?: Resource): { execCommand: string[] | undefined };
25-
};
26-
}
18+
// interface IExtensionApi {
19+
// ready: Promise<void>;
20+
// settings: {
21+
// getExecutionDetails(resource?: Resource): { execCommand: string[] | undefined };
22+
// };
23+
// }
2724

2825
/**
2926
* Details about a Python interpreter.
@@ -70,20 +67,18 @@ async function activateEnvsExtension(): Promise<Extension<any> | undefined> {
7067
return extension;
7168
}
7269

73-
/**
74-
* Gets the Python extension's API interface.
75-
* @returns The Python extension API or undefined if not available
76-
*/
77-
async function getPythonExtensionAPI(): Promise<IExtensionApi | undefined> {
78-
const extension = await activateExtension();
79-
return extension?.exports as IExtensionApi;
70+
async function getPythonEnviromentExtensionAPI(): Promise<PythonEnvironmentApi> {
71+
// Load the Python extension API
72+
await activateEnvsExtension();
73+
return await PythonEnvsExtension.api();
8074
}
8175

82-
/**
83-
* Gets the Python extension's environment API.
84-
* @returns The Python extension environment API
85-
*/
86-
async function getPythonExtensionEnviromentAPI(): Promise<PythonExtension> {
76+
// async function getLegacyPythonExtensionAPI(): Promise<IExtensionApi | undefined> {
77+
// const extension = await activateExtension();
78+
// return extension?.exports as IExtensionApi;
79+
// }
80+
81+
async function getLegacyPythonExtensionEnviromentAPI(): Promise<PythonExtension> {
8782
// Load the Python extension API
8883
await activateExtension();
8984
return await PythonExtension.api();
@@ -95,7 +90,7 @@ async function getPythonExtensionEnviromentAPI(): Promise<PythonExtension> {
9590
*/
9691
export async function initializePython(disposables: Disposable[]): Promise<void> {
9792
try {
98-
const api = await getPythonExtensionEnviromentAPI();
93+
const api = await getLegacyPythonExtensionEnviromentAPI();
9994

10095
if (api) {
10196
disposables.push(
@@ -140,86 +135,115 @@ export async function runPythonExtensionCommand(command: string, ...rest: any[])
140135
* @returns Array of command components or undefined if not available
141136
*/
142137
export async function getSettingsPythonPath(resource?: Uri): Promise<string[] | undefined> {
143-
const api = await getPythonExtensionAPI();
144-
return api?.settings.getExecutionDetails(resource).execCommand;
138+
// const api = await getLegacyPythonExtensionAPI();
139+
// return api?.settings.getExecutionDetails(resource).execCommand;
140+
141+
const apiNew = await getPythonEnviromentExtensionAPI();
142+
const abc: PythonEnvironment[] = await apiNew.getEnvironments(resource || 'all');
143+
console.log('Python envs:', abc);
144+
return undefined;
145145
}
146146

147-
/**
148-
* Returns the environment variables used by the extension for a resource, which includes the custom
149-
* variables configured by user in `.env` files.
150-
* @param resource Optional workspace resource to get environment variables for
151-
* @returns Environment variables object
152-
*/
153-
export async function getEnvironmentVariables(resource?: Resource): Promise<EnvironmentVariables> {
154-
const api = await getPythonExtensionEnviromentAPI();
155-
return Promise.resolve(api.environments.getEnvironmentVariables(resource));
147+
export async function getEnvironmentVariables(resource?: Resource) {
148+
const api = await getLegacyPythonExtensionEnviromentAPI();
149+
return api.environments.getEnvironmentVariables(resource);
156150
}
157151

158-
/**
159-
* Returns details for the given environment, or `undefined` if the env is invalid.
160-
* @param env Environment to resolve (can be Environment object, path, or string)
161-
* @returns Resolved environment details
162-
*/
163152
export async function resolveEnvironment(
164153
env: Environment | EnvironmentPath | string,
154+
): Promise<PythonEnvironment | undefined> {
155+
// const api = await getLegacyPythonExtensionEnviromentAPI();
156+
// return api.environments.resolveEnvironment(env);
157+
158+
const apiNew = await getPythonEnviromentExtensionAPI();
159+
160+
// Handle different input types for the new API
161+
if (typeof env === 'string') {
162+
// Convert string path to Uri for the new API
163+
return apiNew.resolveEnvironment(Uri.file(env));
164+
} else if (typeof env === 'object' && 'path' in env) {
165+
// EnvironmentPath has a uri property
166+
return apiNew.resolveEnvironment(Uri.file(env.path));
167+
} else {
168+
return undefined;
169+
}
170+
}
171+
172+
export async function legacyResolveEnvironment(
173+
env: Environment | EnvironmentPath | string,
165174
): Promise<ResolvedEnvironment | undefined> {
166-
const api = await getPythonExtensionEnviromentAPI();
175+
const api = await getLegacyPythonExtensionEnviromentAPI();
167176
return api.environments.resolveEnvironment(env);
168177
}
169178

170-
/**
171-
* Returns the environment configured by user in settings. Note that this can be an invalid environment, use
172-
* resolve the environment to get full details.
173-
* @param resource Optional workspace resource to get active environment for
174-
* @returns Path to the active environment
175-
*/
176-
export async function getActiveEnvironmentPath(resource?: Resource): Promise<EnvironmentPath> {
177-
const api = await getPythonExtensionEnviromentAPI();
179+
export async function getLegacyActiveEnvironmentPath(resource?: Resource) {
180+
const api = await getLegacyPythonExtensionEnviromentAPI();
178181
return api.environments.getActiveEnvironmentPath(resource);
179182
}
180183

184+
export async function getActiveEnvironmentPath(resource?: Resource): Promise<PythonEnvironment | undefined> {
185+
const api = await getPythonEnviromentExtensionAPI();
186+
187+
// Convert Resource to Uri if it exists
188+
let resourceUri: Uri | undefined;
189+
if (resource instanceof Uri) {
190+
resourceUri = resource;
191+
} else if (resource && 'uri' in resource) {
192+
// WorkspaceFolder type
193+
resourceUri = resource.uri;
194+
}
195+
196+
return api.getEnvironment(resourceUri);
197+
}
198+
181199
/**
182-
* Gets detailed information about the active Python interpreter.
183-
* @param resource Optional workspace resource to get interpreter details for
184-
* @returns Interpreter details including path and resource information
200+
* Gets Python interpreter details using the legacy Python extension API.
201+
*
202+
* This function retrieves the active Python environment for a given resource using the
203+
* legacy @vscode/python-extension API. It resolves the environment to get the executable
204+
* path and handles path quoting for paths containing spaces.
205+
*
206+
* @param resource Optional URI to specify the workspace/folder context for interpreter selection
207+
* @returns Promise resolving to interpreter details containing the executable path and resource
185208
*/
186-
export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
187-
const api = await getPythonExtensionEnviromentAPI();
209+
export async function getLegacyInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
210+
const api = await getLegacyPythonExtensionEnviromentAPI();
211+
188212
const environment = await api.environments.resolveEnvironment(api.environments.getActiveEnvironmentPath(resource));
189213
if (environment?.executable.uri) {
190214
return { path: [environment?.executable.uri.fsPath], resource };
191215
}
192216
return { path: undefined, resource };
193217
}
194218

195-
/**
196-
* Checks if any Python interpreters are available in the system.
197-
* @returns True if interpreters are found, false otherwise
198-
*/
199-
export async function hasInterpreters(): Promise<boolean> {
200-
const api = await getPythonExtensionEnviromentAPI();
201-
const onAddedToCollection = createDeferred();
202-
api.environments.onDidChangeEnvironments(async () => {
203-
if (api.environments.known) {
204-
onAddedToCollection.resolve();
205-
}
206-
});
207-
const initialEnvs = api.environments.known;
208-
if (initialEnvs.length > 0) {
209-
return true;
219+
export function quoteStringIfNecessary(arg: string): string {
220+
// Always return if already quoted to avoid double-quoting
221+
if (arg.startsWith('"') && arg.endsWith('"')) {
222+
return arg;
210223
}
211-
// Initiates a refresh of Python environments within the specified scope.
212-
await Promise.race([onAddedToCollection.promise, api?.environments.refreshEnvironments()]);
213224

214-
return api.environments.known.length > 0;
225+
// Quote if contains common shell special characters that are problematic across multiple shells
226+
// Includes: space, &, |, <, >, ;, ', ", `, (, ), [, ], {, }, $
227+
const needsQuoting = /[\s&|<>;'"`()\[\]{}$]/.test(arg);
228+
229+
return needsQuoting ? `"${arg}"` : arg;
215230
}
216231

217-
/**
218-
* Gets environments known to the extension at the time of fetching the property. Note this may not
219-
* contain all environments in the system as a refresh might be going on.
220-
* @returns Array of known Python environments
221-
*/
222-
export async function getInterpreters(): Promise<readonly Environment[]> {
223-
const api = await getPythonExtensionEnviromentAPI();
224-
return api.environments.known || [];
232+
export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
233+
const api = await getPythonEnviromentExtensionAPI();
234+
235+
// A promise that resolves to the current Python environment, or undefined if none is set.
236+
const env: PythonEnvironment | undefined = await api.getEnvironment(resource);
237+
// resolve the environment to get full details
238+
const resolvedEnv = env ? await api.resolveEnvironment(env?.environmentPath) : undefined;
239+
const executablePath = resolvedEnv?.execInfo.activatedRun?.executable
240+
? resolvedEnv.execInfo.activatedRun.executable
241+
: resolvedEnv?.execInfo.run.executable;
242+
243+
// Quote the executable path if necessary
244+
const a: IInterpreterDetails = {
245+
path: executablePath ? [quoteStringIfNecessary(executablePath)] : undefined,
246+
resource,
247+
};
248+
return a;
225249
}

src/extension/common/settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Licensed under the MIT License.
33

44
import { ConfigurationChangeEvent, ConfigurationTarget, Uri, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
5-
import { getInterpreterDetails } from './python';
65
import { getConfiguration, getWorkspaceFolder, getWorkspaceFolders } from './vscodeapi';
76
import { isUnitTestExecution } from './constants';
87
import { VersionInfo } from '@vscode/python-extension';
8+
import { getInterpreterDetails } from './python';
99

1010
export interface ISettings {
1111
workspace: string;

src/extension/debugger/adapter/factory.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import { getInterpreterDetails, resolveEnvironment, runPythonExtensionCommand }
2323
import { Commands, EXTENSION_ROOT_DIR } from '../../common/constants';
2424
import { Common, DebugConfigStrings, Interpreters } from '../../common/utils/localize';
2525
import { IPersistentStateFactory } from '../../common/types';
26-
import { ResolvedEnvironment } from '@vscode/python-extension';
2726
import { fileToCommandArgumentForPythonExt } from '../../common/stringUtils';
27+
import { PythonEnvironment } from '../../envExtApi';
2828

2929
// persistent state names, exported to make use of in testing
3030
export enum debugStateKeys {
@@ -177,15 +177,32 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac
177177
}
178178
}
179179

180-
private async getExecutableCommand(interpreter: ResolvedEnvironment | undefined): Promise<string[]> {
180+
/**
181+
* Extracts the executable command from a resolved Python environment.
182+
*
183+
* This function takes a resolved Python environment and returns the path to the Python
184+
* executable as a string array suitable for spawning processes. It also performs version
185+
* validation, showing a deprecation warning if the Python version is below 3.9.
186+
*
187+
* @param interpreter The resolved Python environment containing executable path and version info
188+
* @returns Promise resolving to an array containing the Python executable path, or empty array if no interpreter
189+
*/
190+
private async getExecutableCommand(interpreter: PythonEnvironment | undefined): Promise<string[]> {
181191
if (interpreter) {
182-
if (
183-
(interpreter.version?.major ?? 0) < 3 ||
184-
((interpreter.version?.major ?? 0) <= 3 && (interpreter.version?.minor ?? 0) < 9)
185-
) {
192+
const executablePath = interpreter.execInfo.activatedRun?.executable ?? interpreter.execInfo.run.executable;
193+
const version = interpreter.version;
194+
195+
// Parse version string (e.g., "3.8.10" -> major: 3, minor: 8)
196+
const parseMajorMinor = (v: string) => {
197+
const m = v.match(/^(\d+)(?:\.(\d+))?/);
198+
return { major: m ? Number(m[1]) : 0, minor: m && m[2] ? Number(m[2]) : 0 };
199+
};
200+
const { major, minor } = parseMajorMinor(version || '');
201+
202+
if (major < 3 || (major <= 3 && minor < 9)) {
186203
this.showDeprecatedPythonMessage();
187204
}
188-
return interpreter.path.length > 0 ? [interpreter.path] : [];
205+
return executablePath ? [executablePath] : [];
189206
}
190207
return [];
191208
}

0 commit comments

Comments
 (0)