Skip to content

Commit 002bdc2

Browse files
Fix no interpreter error message (#57)
* Add verification for interpreter in debugAdapter * Fix tests * Add error * Fix unused import * Fix tests * fix import error
1 parent c415fd3 commit 002bdc2

File tree

4 files changed

+59
-104
lines changed

4 files changed

+59
-104
lines changed

src/extension/common/python.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ interface IExtensionApi {
2626
known: Environment[];
2727
getActiveEnvironmentPath(resource?: Resource): EnvironmentPath;
2828
resolveEnvironment(
29-
environment: Environment | EnvironmentPath | string,
29+
environment: Environment | EnvironmentPath | string | undefined,
3030
): Promise<ResolvedEnvironment | undefined>;
3131
readonly onDidChangeActiveEnvironmentPath: Event<ActiveEnvironmentPathChangeEvent>;
3232
getEnvironmentVariables(resource?: Resource): EnvironmentVariables;
@@ -80,17 +80,6 @@ export async function initializePython(disposables: Disposable[]): Promise<void>
8080
}
8181
}
8282

83-
export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
84-
const api = await getPythonExtensionAPI();
85-
const environment = await api?.environments.resolveEnvironment(
86-
api?.environments.getActiveEnvironmentPath(resource),
87-
);
88-
if (environment?.executable.uri) {
89-
return { path: [environment?.executable.uri.fsPath], resource };
90-
}
91-
return { path: undefined, resource };
92-
}
93-
9483
export async function getDebuggerPath(): Promise<string | undefined> {
9584
const api = await getPythonExtensionAPI();
9685
return api?.debug.getDebuggerPackagePath();
@@ -111,7 +100,7 @@ export async function getEnvironmentVariables(resource?: Resource) {
111100
return api?.environments.getEnvironmentVariables(resource);
112101
}
113102

114-
export async function resolveEnvironment(env: Environment | EnvironmentPath | string) {
103+
export async function resolveEnvironment(env: Environment | EnvironmentPath | string | undefined) {
115104
const api = await getPythonExtensionAPI();
116105
return api?.environments.resolveEnvironment(env);
117106
}
@@ -121,6 +110,17 @@ export async function getActiveEnvironmentPath(resource?: Resource) {
121110
return api?.environments.getActiveEnvironmentPath(resource);
122111
}
123112

113+
export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
114+
const api = await getPythonExtensionAPI();
115+
const environment = await api?.environments.resolveEnvironment(
116+
api?.environments.getActiveEnvironmentPath(resource),
117+
);
118+
if (environment?.executable.uri) {
119+
return { path: [environment?.executable.uri.fsPath], resource };
120+
}
121+
return { path: undefined, resource };
122+
}
123+
124124
export async function hasInterpreters() {
125125
const api = await getPythonExtensionAPI();
126126
if (api) {

src/extension/common/utils/localize.ts

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export namespace AttachProcess {
1313
}
1414

1515
export namespace DebugConfigStrings {
16+
export const debugStopped = l10n.t('Debug Stopped');
1617
export const selectConfiguration = {
1718
title: l10n.t('Select a debug configuration'),
1819
placeholder: l10n.t('Debug Configuration'),
@@ -142,44 +143,6 @@ export namespace DebugConfigStrings {
142143
}
143144
}
144145

145-
export namespace Diagnostics {
146-
export const warnSourceMaps = l10n.t(
147-
'Source map support is enabled in the Python Extension, this will adversely impact performance of the extension.',
148-
);
149-
export const disableSourceMaps = l10n.t('Disable Source Map Support');
150-
export const warnBeforeEnablingSourceMaps = l10n.t(
151-
'Enabling source map support in the Python Extension will adversely impact performance of the extension.',
152-
);
153-
export const enableSourceMapsAndReloadVSC = l10n.t('Enable and reload Window.');
154-
export const lsNotSupported = l10n.t(
155-
'Your operating system does not meet the minimum requirements of the Python Language Server. Reverting to the alternative autocompletion provider, Jedi.',
156-
);
157-
export const invalidPythonPathInDebuggerSettings = l10n.t(
158-
'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Interpreter" in the status bar.',
159-
);
160-
export const invalidPythonPathInDebuggerLaunch = l10n.t('The Python path in your debug configuration is invalid.');
161-
export const invalidDebuggerTypeDiagnostic = l10n.t(
162-
'Your launch.json file needs to be updated to change the "pythonExperimental" debug configurations to use the "python" debugger type, otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?',
163-
);
164-
export const consoleTypeDiagnostic = l10n.t(
165-
'Your launch.json file needs to be updated to change the console type string from "none" to "internalConsole", otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?',
166-
);
167-
export const justMyCodeDiagnostic = l10n.t(
168-
'Configuration "debugStdLib" in launch.json is no longer supported. It\'s recommended to replace it with "justMyCode", which is the exact opposite of using "debugStdLib". Would you like to automatically update your launch.json file to do that?',
169-
);
170-
export const yesUpdateLaunch = l10n.t('Yes, update launch.json');
171-
export const invalidTestSettings = l10n.t(
172-
'Your settings needs to be updated to change the setting "python.unitTest." to "python.testing.", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?',
173-
);
174-
export const updateSettings = l10n.t('Yes, update settings');
175-
export const checkIsort5UpgradeGuide = l10n.t(
176-
'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.',
177-
);
178-
export const pylanceDefaultMessage = l10n.t(
179-
"The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn how to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance's license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).",
180-
);
181-
}
182-
183146
export namespace Common {
184147
export const loadingExtension = l10n.t('Python Debugger extension loading...');
185148
export const doNotShowAgain = l10n.t('Do not show again');

src/extension/debugger/adapter/factory.ts

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,15 @@ import {
1616
} from 'vscode';
1717
import { AttachRequestArguments, LaunchRequestArguments } from '../../types';
1818
import { IDebugAdapterDescriptorFactory } from '../types';
19-
import { showErrorMessage } from '../../common/vscodeapi';
19+
import { executeCommand, showErrorMessage } from '../../common/vscodeapi';
2020
import { traceLog, traceVerbose } from '../../common/log/logging';
2121
import { EventName } from '../../telemetry/constants';
2222
import { sendTelemetryEvent } from '../../telemetry';
23-
import {
24-
getActiveEnvironmentPath,
25-
getInterpreters,
26-
hasInterpreters,
27-
resolveEnvironment,
28-
runPythonExtensionCommand,
29-
} from '../../common/python';
23+
import { getActiveEnvironmentPath, resolveEnvironment, runPythonExtensionCommand } from '../../common/python';
3024
import { Commands, EXTENSION_ROOT_DIR } from '../../common/constants';
31-
import { Common, Interpreters } from '../../common/utils/localize';
25+
import { Common, DebugConfigStrings, Interpreters } from '../../common/utils/localize';
3226
import { IPersistentStateFactory } from '../../common/types';
3327
import { Environment } from '../../common/pythonTypes';
34-
import { ignoreErrors } from '../../common/promiseUtils';
3528

3629
// persistent state names, exported to make use of in testing
3730
export enum debugStateKeys {
@@ -45,7 +38,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac
4538
public async createDebugAdapterDescriptor(
4639
session: DebugSession,
4740
_executable: DebugAdapterExecutable | undefined,
48-
): Promise<DebugAdapterDescriptor> {
41+
): Promise<DebugAdapterDescriptor | undefined> {
4942
const configuration = session.configuration as LaunchRequestArguments | AttachRequestArguments;
5043

5144
// There are four distinct scenarios here:
@@ -98,16 +91,14 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac
9891
traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`);
9992
sendTelemetryEvent(EventName.DEBUG_ADAPTER_USING_WHEELS_PATH, undefined, { usingWheels: true });
10093
return new DebugAdapterExecutable(executable, args);
94+
} else {
95+
throw new Error(DebugConfigStrings.debugStopped);
10196
}
102-
103-
// Unlikely scenario.
104-
throw new Error('Debug Adapter Executable not provided');
10597
}
10698

107-
// FIX THIS
10899
/**
109100
* Get the python executable used to launch the Python Debug Adapter.
110-
* In the case of `attach` scenarios, just use the workspace interpreter, else first available one.
101+
* In the case of `attach` scenarios, just use the workspace interpreter.
111102
* It is unlike user won't have a Python interpreter
112103
*
113104
* @private
@@ -128,21 +119,25 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac
128119

129120
const resourceUri = workspaceFolder ? workspaceFolder.uri : undefined;
130121
const interpreterPath = await getActiveEnvironmentPath(resourceUri);
122+
const interpreter = await resolveEnvironment(interpreterPath);
131123

132-
if (interpreterPath) {
124+
if (interpreter?.path) {
133125
traceVerbose(`Selecting active interpreter as Python Executable for DA '${interpreterPath}'`);
134126
return this.getExecutableCommand(await resolveEnvironment(interpreterPath));
135127
}
136128

137-
await hasInterpreters(); // Wait until we know whether we have an interpreter
138-
const interpreters = await getInterpreters();
139-
if (interpreters.length === 0) {
140-
ignoreErrors(this.notifySelectInterpreter());
141-
return [];
129+
const prompts = [Interpreters.changePythonInterpreter];
130+
const selection = await showErrorMessage(
131+
l10n.t(
132+
'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Interpreter" in the status bar.',
133+
),
134+
{ modal: true },
135+
...prompts,
136+
);
137+
if (selection === Interpreters.changePythonInterpreter) {
138+
await executeCommand(Commands.Set_Interpreter);
142139
}
143-
144-
traceVerbose(`Picking first available interpreter to launch the DA '${interpreters[0].path}'`);
145-
return this.getExecutableCommand(interpreters[0]);
140+
return [];
146141
}
147142

148143
private async showDeprecatedPythonMessage() {
@@ -185,16 +180,4 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac
185180
}
186181
return [];
187182
}
188-
189-
/**
190-
* Notify user about the requirement for Python.
191-
* Unlikely scenario, as ex expect users to have Python in order to use the extension.
192-
* However it is possible to ignore the warnings and continue using the extension.
193-
*
194-
* @private
195-
* @memberof DebugAdapterDescriptorFactory
196-
*/
197-
private async notifySelectInterpreter() {
198-
await showErrorMessage(l10n.t('Please install Python or select a Python Interpreter to use the debugger.'));
199-
}
200183
}

src/test/unittest/adapter/factory.unit.test.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import * as vscodeApi from '../../../extension/common/vscodeapi';
2222
import { EXTENSION_ROOT_DIR } from '../../../extension/common/constants';
2323
import { Architecture } from '../../../extension/common/platform';
2424
import * as pythonApi from '../../../extension/common/python';
25+
import { DebugConfigStrings } from '../../../extension/common/utils/localize';
2526

2627
use(chaiAsPromised);
2728

@@ -125,21 +126,14 @@ suite('Debugging - Adapter Factory', () => {
125126
assert.deepStrictEqual(descriptor, debugExecutable);
126127
});
127128

128-
test('Return the path of the first available interpreter as the current python path, configuration.pythonPath is not defined and there is no active interpreter', async () => {
129-
const session = createSession({});
130-
const debugExecutable = new DebugAdapterExecutable(pythonPath, [debugAdapterPath]);
131-
132-
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
133-
134-
assert.deepStrictEqual(descriptor, debugExecutable);
135-
});
136-
137-
test('Display a message if no python interpreter is set', async () => {
138-
getInterpretersStub.returns([]);
129+
test.only('Display a message if no python interpreter is set', async () => {
130+
getActiveEnvironmentPathStub.resolves(undefined);
139131
const session = createSession({});
140132
const promise = factory.createDebugAdapterDescriptor(session, nodeExecutable);
141133

142-
await expect(promise).to.eventually.be.rejectedWith('Debug Adapter Executable not provided');
134+
await expect(promise).to.eventually.be.rejectedWith(DebugConfigStrings.debugStopped);
135+
136+
//check error message
143137
sinon.assert.calledOnce(showErrorMessageStub);
144138
});
145139

@@ -191,11 +185,10 @@ suite('Debugging - Adapter Factory', () => {
191185
test('Return Debug Adapter executable if request is "attach", and listen is specified', async () => {
192186
const session = createSession({ request: 'attach', listen: { port: 5678, host: 'localhost' } });
193187
const debugExecutable = new DebugAdapterExecutable(pythonPath, [debugAdapterPath]);
194-
195188
getActiveEnvironmentPathStub.resolves(interpreter.path);
196189
resolveEnvironmentStub.resolves(interpreter);
197-
198190
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
191+
199192
assert.deepStrictEqual(descriptor, debugExecutable);
200193
});
201194

@@ -223,6 +216,9 @@ suite('Debugging - Adapter Factory', () => {
223216
EXTENSION_ROOT_DIR,
224217
]);
225218

219+
getActiveEnvironmentPathStub.resolves(interpreter.path);
220+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
221+
226222
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
227223

228224
assert.deepStrictEqual(descriptor, debugExecutable);
@@ -232,6 +228,9 @@ suite('Debugging - Adapter Factory', () => {
232228
const session = createSession({});
233229
const debugExecutable = new DebugAdapterExecutable(pythonPath, [debugAdapterPath]);
234230

231+
getActiveEnvironmentPathStub.resolves(interpreter.path);
232+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
233+
235234
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
236235

237236
assert.deepStrictEqual(descriptor, debugExecutable);
@@ -241,20 +240,28 @@ suite('Debugging - Adapter Factory', () => {
241240
const session = createSession({ logToFile: false });
242241
const debugExecutable = new DebugAdapterExecutable(pythonPath, [debugAdapterPath]);
243242

243+
getActiveEnvironmentPathStub.resolves(interpreter.path);
244+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
245+
244246
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
245247

246248
assert.deepStrictEqual(descriptor, debugExecutable);
247249
});
248250

249251
test('Send attach to local process telemetry if attaching to a local process', async () => {
250252
const session = createSession({ request: 'attach', processId: 1234 });
253+
getActiveEnvironmentPathStub.resolves(interpreter.path);
254+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
255+
251256
await factory.createDebugAdapterDescriptor(session, nodeExecutable);
252257

253258
assert.ok(Reporter.eventNames.includes(EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS));
254259
});
255260

256261
test("Don't send any telemetry if not attaching to a local process", async () => {
257262
const session = createSession({});
263+
getActiveEnvironmentPathStub.resolves(interpreter.path);
264+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
258265

259266
await factory.createDebugAdapterDescriptor(session, nodeExecutable);
260267

@@ -265,7 +272,8 @@ suite('Debugging - Adapter Factory', () => {
265272
const customAdapterPath = 'custom/debug/adapter/path';
266273
const session = createSession({ debugAdapterPath: customAdapterPath });
267274
const debugExecutable = new DebugAdapterExecutable(pythonPath, [customAdapterPath]);
268-
275+
getActiveEnvironmentPathStub.resolves(interpreter.path);
276+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
269277
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
270278

271279
assert.deepStrictEqual(descriptor, debugExecutable);
@@ -292,7 +300,8 @@ suite('Debugging - Adapter Factory', () => {
292300
test('Do not use "python" to spawn the debug adapter', async () => {
293301
const session = createSession({ python: '/bin/custompy' });
294302
const debugExecutable = new DebugAdapterExecutable(pythonPath, [debugAdapterPath]);
295-
303+
getActiveEnvironmentPathStub.resolves(interpreter.path);
304+
resolveEnvironmentStub.withArgs(interpreter.path).resolves(interpreter);
296305
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);
297306

298307
assert.deepStrictEqual(descriptor, debugExecutable);

0 commit comments

Comments
 (0)