Skip to content

Commit 34c70bb

Browse files
Kartik Rajdebontekarthiknadigmitchell-aspetetronic
committed
Merge latest upstream python extension
-------------------- Commit message for microsoft/vscode-python@fc72be9: Show `Python: Report issue` command in palette regardless of whether a Python file is opened (microsoft/vscode-python#20726) Closes microsoft/vscode-python#20723 -------------------- Commit message for microsoft/vscode-python@c18e8c9: Detect ActiveState Python runtimes (microsoft/vscode-python#20534) Closes microsoft/vscode-python#20532 -------------------- Commit message for microsoft/vscode-python@2152cd9: Don't set `formatOnType` for auto-indent experiment if it's already set (microsoft/vscode-python#20710) -------------------- Commit message for microsoft/vscode-python@995b0bc: Add support for 'back' to all create env UI. (microsoft/vscode-python#20693) Closes microsoft/vscode-python#20274 ### Usage This change allows callers of the Create Environment command to handle `Back` and `Cancel`: ``` typescript let result: CreateEnvironmentResult | undefined; try { const result = await commands.executeCommand("python.createEnvironment", {showBackButton: true}); } catch(e) { // error while creating environment } if (result?.action === 'Back') { // user clicked Back } if (result?.action === 'Cancel') { // user pressed escape or Cancel } ``` I decided to go with `result?.action` because we don't have a npm package for python extension API so catching particular exception might be error prone with `ex instanceof <error>`. We will provide a proper interface via `api.environments` for create environment, and contribution to create environment. Until that point this command will provide the stop gap. ### Notes 1. I did not use the multi-step input that is used in the rest of the extension because, the existing implementation does not have context. Consider the following scenario: venv -> workspace select -> python select -> packages. Assume that there is only one workspace, and we don't show the workspace selection UI, that decision is done inside the workspace step. So, if there is only 1 workspace it is a short circuit to next step. User is on python selection and clicks `back`, workspace selection short circuits to next step which is python selection. So, from user perspective, back does not work. This can be fixed by sending context that the reason control moved to previous step was because user clicked on back. 2. This makes a change to old multi step API to rethrow the exception, if user hits `back` and the current step has no steps to go back to. -------------------- Commit message for microsoft/vscode-python@f3ecbf5: Fix-conda-version-parsing (microsoft/vscode-python#20674) -------------------- Commit message for microsoft/vscode-python@a6a6f50: Inactive pytest run command (microsoft/vscode-python#20653) Here the new flow is created but kept inactive for the pytest execution -------------------- Commit message for microsoft/vscode-python@2202fbe: Call the correct API to determine if a user is in treatment or control group (microsoft/vscode-python#20690) Closes microsoft/vscode-python#20183 -------------------- Commit message for microsoft/vscode-python@b0ab10d: Only use activated environment from terminal if VSCode was launched via CLI (microsoft/vscode-python#20667) Closes microsoft/vscode-python#20644 -------------------- Commit message for microsoft/vscode-python@02a92fc: Ensure interpreter path isn't truncated for workspace-relative paths when storing value (microsoft/vscode-python#20661) For microsoft/vscode-python#20660 I'm not quite sure why this was done. It doesn't make sense to do this only for display. -------------------- Commit message for microsoft/vscode-python@377067f: Use correct API to get interpreter path for language servers (microsoft/vscode-python#20656) For microsoft/vscode-python#20644 closes microsoft/vscode-python#20657 -------------------- Commit message for microsoft/vscode-python@cd6ca9d: Remove `isort` extension dependency (microsoft/vscode-python#20577) Closes microsoft/vscode-python#20586 Lead-authored-by: Kartik Raj <[email protected]> Co-authored-by: Erik De Bonte <[email protected]> Co-authored-by: Karthik Nadig <[email protected]> Co-authored-by: mitchell <[email protected]> Co-authored-by: Pete Farland <[email protected]> Co-authored-by: Eleanor Boyd <[email protected]>
1 parent 758eae8 commit 34c70bb

File tree

84 files changed

+1799
-510
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1799
-510
lines changed

extensions/positron-python/.github/workflows/pr-check.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,6 @@ jobs:
266266
working-directory: ${{ env.special-working-directory }}
267267
if: matrix.test-suite == 'single-workspace'
268268

269-
- name: Run multi-workspace tests
270-
env:
271-
CI_PYTHON_VERSION: ${{ matrix.python }}
272-
uses: GabrielBB/[email protected]
273-
with:
274-
run: npm run testMultiWorkspace
275-
working-directory: ${{ env.special-working-directory }}
276-
if: matrix.test-suite == 'multi-workspace'
277-
278269
- name: Run debugger tests
279270
env:
280271
CI_PYTHON_VERSION: ${{ matrix.python }}

extensions/positron-python/gulpfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async function addExtensionPackDependencies() {
8282
// extension dependencies need not be installed during development
8383
const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8');
8484
const packageJson = JSON.parse(packageJsonContents);
85-
packageJson.extensionPack = ['ms-toolsai.jupyter', 'ms-python.vscode-pylance', 'ms-python.isort'].concat(
85+
packageJson.extensionPack = ['ms-toolsai.jupyter', 'ms-python.vscode-pylance'].concat(
8686
packageJson.extensionPack ? packageJson.extensionPack : [],
8787
);
8888
await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8');

extensions/positron-python/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,12 @@
383383
"scope": "window",
384384
"type": "string"
385385
},
386+
"python.activeStateToolPath": {
387+
"default": "state",
388+
"description": "%python.activeStateToolPath.description%",
389+
"scope": "machine-overridable",
390+
"type": "string"
391+
},
386392
"python.autoComplete.extraPaths": {
387393
"default": [],
388394
"description": "%python.autoComplete.extraPaths.description%",
@@ -1621,7 +1627,7 @@
16211627
"category": "Python",
16221628
"command": "python.reportIssue",
16231629
"title": "%python.command.python.reportIssue.title%",
1624-
"when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python"
1630+
"when": "!virtualWorkspace && shellExecutionSupported"
16251631
},
16261632
{
16271633
"category": "Test",

extensions/positron-python/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"python.command.python.launchTensorBoard.title": "Launch TensorBoard",
2828
"python.command.python.refreshTensorBoard.title": "Refresh TensorBoard",
2929
"python.menu.createNewFile.title": "Python File",
30+
"python.activeStateToolPath.description": "Path to the State Tool executable for ActiveState runtimes (version 0.36+).",
3031
"python.autoComplete.extraPaths.description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.",
3132
"python.condaPath.description": "Path to the conda executable to use for activation (version 4.4+).",
3233
"python.defaultInterpreterPath.description": "Path to default Python to use when extension loads up for the first time, no longer used once an interpreter is selected for the workspace. See [here](https://aka.ms/AAfekmf) to understand when this is used",

extensions/positron-python/pythonFiles/create_venv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,14 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
154154
if pip_installed:
155155
upgrade_pip(venv_path)
156156

157-
if args.requirements:
158-
print(f"VENV_INSTALLING_REQUIREMENTS: {args.requirements}")
159-
install_requirements(venv_path, args.requirements)
160-
161157
if args.toml:
162158
print(f"VENV_INSTALLING_PYPROJECT: {args.toml}")
163159
install_toml(venv_path, args.extras)
164160

161+
if args.requirements:
162+
print(f"VENV_INSTALLING_REQUIREMENTS: {args.requirements}")
163+
install_requirements(venv_path, args.requirements)
164+
165165

166166
if __name__ == "__main__":
167167
main(sys.argv[1:])

extensions/positron-python/pythonFiles/sortImports.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

extensions/positron-python/resources/report_issue_user_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"envFile": "placeholder",
88
"venvPath": "placeholder",
99
"venvFolders": "placeholder",
10+
"activeStateToolPath": "placeholder",
1011
"condaPath": "placeholder",
1112
"pipenvPath": "placeholder",
1213
"poetryPath": "placeholder",

extensions/positron-python/src/client/activation/languageClientMiddlewareBase.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import {
1313
import { ConfigurationItem } from 'vscode-languageserver-protocol';
1414

1515
import { HiddenFilePrefix } from '../common/constants';
16-
import { IConfigurationService } from '../common/types';
1716
import { createDeferred, isThenable } from '../common/utils/async';
1817
import { StopWatch } from '../common/utils/stopWatch';
1918
import { IEnvironmentVariablesProvider } from '../common/variables/types';
19+
import { IInterpreterService } from '../interpreter/contracts';
2020
import { IServiceContainer } from '../ioc/types';
2121
import { EventName } from '../telemetry/constants';
2222
import { LanguageServerType } from './types';
@@ -66,7 +66,7 @@ export class LanguageClientMiddlewareBase implements Middleware {
6666
return next(params, token);
6767
}
6868

69-
const configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
69+
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
7070
const envService = this.serviceContainer.get<IEnvironmentVariablesProvider>(IEnvironmentVariablesProvider);
7171

7272
let settings = next(params, token);
@@ -87,9 +87,10 @@ export class LanguageClientMiddlewareBase implements Middleware {
8787
const settingDict: LSPObject & { pythonPath: string; _envPYTHONPATH: string } = settings[
8888
i
8989
] as LSPObject & { pythonPath: string; _envPYTHONPATH: string };
90-
9190
settingDict.pythonPath =
92-
(await this.getPythonPathOverride(uri)) ?? configService.getSettings(uri).pythonPath;
91+
(await this.getPythonPathOverride(uri)) ??
92+
(await interpreterService.getActiveInterpreter(uri))?.path ??
93+
'python';
9394

9495
const env = await envService.getEnvironmentVariables(uri);
9596
const envPYTHONPATH = env.PYTHONPATH;

extensions/positron-python/src/client/activation/node/analysisOptions.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,41 @@ export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOpt
4343
}
4444

4545
private async isAutoIndentEnabled() {
46-
const editorConfig = this.getPythonSpecificEditorSection();
47-
const formatOnTypeInspect = editorConfig.inspect(FORMAT_ON_TYPE_CONFIG_SETTING);
48-
const formatOnTypeSetForPython = formatOnTypeInspect?.globalLanguageValue !== undefined;
46+
let editorConfig = this.getPythonSpecificEditorSection();
4947

50-
const inExperiment = await this.isInAutoIndentExperiment();
51-
// only explicitly enable formatOnType for those who are in the experiment
48+
// Only explicitly enable formatOnType for those who are in the experiment
5249
// but have not explicitly given a value for the setting
53-
if (!formatOnTypeSetForPython && inExperiment) {
54-
await NodeLanguageServerAnalysisOptions.setPythonSpecificFormatOnType(editorConfig, true);
50+
if (!NodeLanguageServerAnalysisOptions.isConfigSettingSetByUser(editorConfig, FORMAT_ON_TYPE_CONFIG_SETTING)) {
51+
const inExperiment = await this.isInAutoIndentExperiment();
52+
if (inExperiment) {
53+
await NodeLanguageServerAnalysisOptions.setPythonSpecificFormatOnType(editorConfig, true);
54+
55+
// Refresh our view of the config settings.
56+
editorConfig = this.getPythonSpecificEditorSection();
57+
}
5558
}
5659

57-
const formatOnTypeEffectiveValue = this.getPythonSpecificEditorSection().get(FORMAT_ON_TYPE_CONFIG_SETTING);
60+
const formatOnTypeEffectiveValue = editorConfig.get(FORMAT_ON_TYPE_CONFIG_SETTING);
5861

5962
return formatOnTypeEffectiveValue;
6063
}
6164

65+
private static isConfigSettingSetByUser(configuration: WorkspaceConfiguration, setting: string): boolean {
66+
const inspect = configuration.inspect(setting);
67+
if (inspect === undefined) {
68+
return false;
69+
}
70+
71+
return (
72+
inspect.globalValue !== undefined ||
73+
inspect.workspaceValue !== undefined ||
74+
inspect.workspaceFolderValue !== undefined ||
75+
inspect.globalLanguageValue !== undefined ||
76+
inspect.workspaceLanguageValue !== undefined ||
77+
inspect.workspaceFolderLanguageValue !== undefined
78+
);
79+
}
80+
6281
private async isInAutoIndentExperiment(): Promise<boolean> {
6382
if (await this.experimentService.inExperiment('pylanceAutoIndent')) {
6483
return true;

extensions/positron-python/src/client/common/configSettings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export class PythonSettings implements IPythonSettings {
9898

9999
public venvFolders: string[] = [];
100100

101+
public activeStateToolPath = '';
102+
101103
public condaPath = '';
102104

103105
public pipenvPath = '';
@@ -254,6 +256,11 @@ export class PythonSettings implements IPythonSettings {
254256

255257
this.venvPath = systemVariables.resolveAny(pythonSettings.get<string>('venvPath'))!;
256258
this.venvFolders = systemVariables.resolveAny(pythonSettings.get<string[]>('venvFolders'))!;
259+
const activeStateToolPath = systemVariables.resolveAny(pythonSettings.get<string>('activeStateToolPath'))!;
260+
this.activeStateToolPath =
261+
activeStateToolPath && activeStateToolPath.length > 0
262+
? getAbsolutePath(activeStateToolPath, workspaceRoot)
263+
: activeStateToolPath;
257264
const condaPath = systemVariables.resolveAny(pythonSettings.get<string>('condaPath'))!;
258265
this.condaPath = condaPath && condaPath.length > 0 ? getAbsolutePath(condaPath, workspaceRoot) : condaPath;
259266
const pipenvPath = systemVariables.resolveAny(pythonSettings.get<string>('pipenvPath'))!;

extensions/positron-python/src/client/common/experiments/service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class ExperimentService implements IExperimentService {
142142
// it means that the value for this experiment was not found on the server.
143143
const treatmentVariable = this.experimentationService.getTreatmentVariable(EXP_CONFIG_ID, experiment);
144144

145-
return treatmentVariable !== undefined;
145+
return treatmentVariable === true;
146146
}
147147

148148
public async getExperimentValue<T extends boolean | number | string>(experiment: string): Promise<T | undefined> {

extensions/positron-python/src/client/common/process/internal/scripts/index.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,6 @@ export function interpreterInfo(): [string[], (out: string) => InterpreterInfoJs
5858
return [args, parse];
5959
}
6060

61-
// sortImports.py
62-
63-
export function sortImports(filename: string, sortArgs?: string[]): [string[], (out: string) => string] {
64-
const script = path.join(SCRIPTS_DIR, 'sortImports.py');
65-
const args = [script, filename, '--diff'];
66-
if (sortArgs) {
67-
args.push(...sortArgs);
68-
}
69-
70-
function parse(out: string) {
71-
// It should just be a diff that the extension will use directly.
72-
return out;
73-
}
74-
75-
return [args, parse];
76-
}
77-
7861
// normalizeSelection.py
7962

8063
export function normalizeSelection(): [string[], (out: string) => string] {

extensions/positron-python/src/client/common/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export interface IPythonSettings {
184184
readonly pythonPath: string;
185185
readonly venvPath: string;
186186
readonly venvFolders: string[];
187+
readonly activeStateToolPath: string;
187188
readonly condaPath: string;
188189
readonly pipenvPath: string;
189190
readonly poetryPath: string;

extensions/positron-python/src/client/common/utils/async.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface Deferred<T> {
2727
readonly rejected: boolean;
2828
readonly completed: boolean;
2929
resolve(value?: T | PromiseLike<T>): void;
30-
reject(reason?: string | Error | Record<string, unknown>): void;
30+
reject(reason?: string | Error | Record<string, unknown> | unknown): void;
3131
}
3232

3333
class DeferredImpl<T> implements Deferred<T> {

extensions/positron-python/src/client/common/utils/localize.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,10 @@ export namespace ToolsExtensions {
466466
export const pylintPromptMessage = l10n.t(
467467
'Use the Pylint extension to enable easier configuration and new features such as quick fixes.',
468468
);
469+
export const isortPromptMessage = l10n.t(
470+
'To use sort imports, please install the isort extension. It provides easier configuration and new features such as code actions.',
471+
);
469472
export const installPylintExtension = l10n.t('Install Pylint extension');
470473
export const installFlake8Extension = l10n.t('Install Flake8 extension');
474+
export const installISortExtension = l10n.t('Install isort extension');
471475
}

extensions/positron-python/src/client/common/utils/multiStepInput.ts

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { inject, injectable } from 'inversify';
99
import { Disposable, QuickInput, QuickInputButton, QuickInputButtons, QuickPick, QuickPickItem, Event } from 'vscode';
1010
import { IApplicationShell } from '../application/types';
11+
import { createDeferred } from './async';
1112

1213
// Borrowed from https://github.com/Microsoft/vscode-extension-samples/blob/master/quickinput-sample/src/multiStepInput.ts
1314
// Why re-invent the wheel :)
@@ -29,7 +30,7 @@ export type InputStep<T extends any> = (input: MultiStepInput<T>, state: T) => P
2930

3031
type buttonCallbackType<T extends QuickPickItem> = (quickPick: QuickPick<T>) => void;
3132

32-
type QuickInputButtonSetup = {
33+
export type QuickInputButtonSetup = {
3334
/**
3435
* Button for an action in a QuickPick.
3536
*/
@@ -164,35 +165,41 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
164165
// so do it after initialization. This ensures quickpick starts with the active
165166
// item in focus when this is true, instead of having scroll position at top.
166167
input.keepScrollPosition = keepScrollPosition;
167-
try {
168-
return await new Promise<MultiStepInputQuickPicResponseType<T, P>>((resolve, reject) => {
169-
disposables.push(
170-
input.onDidTriggerButton(async (item) => {
171-
if (item === QuickInputButtons.Back) {
172-
reject(InputFlowAction.back);
173-
}
174-
if (customButtonSetups) {
175-
for (const customButtonSetup of customButtonSetups) {
176-
if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) {
177-
await customButtonSetup?.callback(input);
178-
}
179-
}
168+
169+
const deferred = createDeferred<T>();
170+
171+
disposables.push(
172+
input.onDidTriggerButton(async (item) => {
173+
if (item === QuickInputButtons.Back) {
174+
deferred.reject(InputFlowAction.back);
175+
input.hide();
176+
}
177+
if (customButtonSetups) {
178+
for (const customButtonSetup of customButtonSetups) {
179+
if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) {
180+
await customButtonSetup?.callback(input);
180181
}
181-
}),
182-
input.onDidChangeSelection((selectedItems) => resolve(selectedItems[0])),
183-
input.onDidHide(() => {
184-
resolve(undefined);
185-
}),
186-
);
187-
if (acceptFilterBoxTextAsSelection) {
188-
disposables.push(
189-
input.onDidAccept(() => {
190-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
191-
resolve(input.value as any);
192-
}),
193-
);
182+
}
194183
}
195-
});
184+
}),
185+
input.onDidChangeSelection((selectedItems) => deferred.resolve(selectedItems[0])),
186+
input.onDidHide(() => {
187+
if (!deferred.completed) {
188+
deferred.resolve(undefined);
189+
}
190+
}),
191+
);
192+
if (acceptFilterBoxTextAsSelection) {
193+
disposables.push(
194+
input.onDidAccept(() => {
195+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
196+
deferred.resolve(input.value as any);
197+
}),
198+
);
199+
}
200+
201+
try {
202+
return await deferred.promise;
196203
} finally {
197204
disposables.forEach((d) => d.dispose());
198205
}
@@ -277,6 +284,9 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
277284
if (err === InputFlowAction.back) {
278285
this.steps.pop();
279286
step = this.steps.pop();
287+
if (step === undefined) {
288+
throw err;
289+
}
280290
} else if (err === InputFlowAction.resume) {
281291
step = this.steps.pop();
282292
} else if (err === InputFlowAction.cancel) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as path from 'path';
5+
import * as fs from 'fs-extra';
6+
import { Extension, extensions } from 'vscode';
7+
import { PVSC_EXTENSION_ID } from '../constants';
8+
9+
export function getExtension<T = unknown>(extensionId: string): Extension<T> | undefined {
10+
return extensions.getExtension(extensionId);
11+
}
12+
13+
export function isExtensionEnabled(extensionId: string): boolean {
14+
return extensions.getExtension(extensionId) !== undefined;
15+
}
16+
17+
export function isExtensionDisabled(extensionId: string): boolean {
18+
// We need an enabled extension to find the extensions dir.
19+
const pythonExt = getExtension(PVSC_EXTENSION_ID);
20+
if (pythonExt) {
21+
let found = false;
22+
fs.readdirSync(path.dirname(pythonExt.extensionPath), { withFileTypes: false }).forEach((s) => {
23+
if (s.toString().startsWith(extensionId)) {
24+
found = true;
25+
}
26+
});
27+
return found;
28+
}
29+
return false;
30+
}

0 commit comments

Comments
 (0)