Skip to content

Commit cd6ca9d

Browse files
authored
Remove isort extension dependency (#20577)
Closes #20586
1 parent fe4c5f1 commit cd6ca9d

File tree

21 files changed

+274
-86
lines changed

21 files changed

+274
-86
lines changed

gulpfile.js

+1-1
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');

pythonFiles/sortImports.py

-14
This file was deleted.

src/client/common/process/internal/scripts/index.ts

-17
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] {

src/client/common/utils/localize.ts

+4
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
}
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+
}

src/client/linters/flake8.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { Product } from '../common/types';
44
import { IServiceContainer } from '../ioc/types';
55
import { traceLog } from '../logging';
66
import { BaseLinter } from './baseLinter';
7+
import { isExtensionEnabled } from './prompts/common';
8+
import { FLAKE8_EXTENSION } from './prompts/flake8Prompt';
79
import { IToolsExtensionPrompt } from './prompts/types';
810
import { ILintMessage } from './types';
911

@@ -15,8 +17,12 @@ export class Flake8 extends BaseLinter {
1517
}
1618

1719
protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
18-
if (await this.prompt.showPrompt()) {
19-
traceLog('LINTING: Skipping linting from Python extension, since Flake8 extension is installed.');
20+
await this.prompt.showPrompt();
21+
22+
if (isExtensionEnabled(this.serviceContainer, FLAKE8_EXTENSION)) {
23+
traceLog(
24+
'LINTING: Skipping linting from Python extension, since Flake8 extension is installed and enabled.',
25+
);
2026
return [];
2127
}
2228

src/client/linters/prompts/common.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { IExperimentService, IExtensions, IPersistentState, IPersistentStateFact
88
import { IServiceContainer } from '../../ioc/types';
99
import { traceLog } from '../../logging';
1010

11-
function isExtensionInstalledButDisabled(extensions: IExtensions, extensionId: string): boolean {
11+
export function isExtensionDisabled(serviceContainer: IServiceContainer, extensionId: string): boolean {
12+
const extensions: IExtensions = serviceContainer.get<IExtensions>(IExtensions);
1213
// When debugging the python extension this `extensionPath` below will point to your repo.
1314
// If you are debugging this feature then set the `extensionPath` to right location after
1415
// the next line.
@@ -26,13 +27,12 @@ function isExtensionInstalledButDisabled(extensions: IExtensions, extensionId: s
2627
return false;
2728
}
2829

29-
export function isExtensionInstalled(serviceContainer: IServiceContainer, extensionId: string): boolean {
30+
/**
31+
* Detects if extension is installed and enabled.
32+
*/
33+
export function isExtensionEnabled(serviceContainer: IServiceContainer, extensionId: string): boolean {
3034
const extensions: IExtensions = serviceContainer.get<IExtensions>(IExtensions);
3135
const extension = extensions.getExtension(extensionId);
32-
if (!extension) {
33-
// The extension you are looking for might be disabled.
34-
return isExtensionInstalledButDisabled(extensions, extensionId);
35-
}
3636
return extension !== undefined;
3737
}
3838

src/client/linters/prompts/flake8Prompt.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { showInformationMessage } from '../../common/vscodeApis/windowApis';
88
import { IServiceContainer } from '../../ioc/types';
99
import { sendTelemetryEvent } from '../../telemetry';
1010
import { EventName } from '../../telemetry/constants';
11-
import { isExtensionInstalled, doNotShowPromptState, inToolsExtensionsExperiment } from './common';
11+
import { doNotShowPromptState, inToolsExtensionsExperiment, isExtensionDisabled, isExtensionEnabled } from './common';
1212
import { IToolsExtensionPrompt } from './types';
1313

1414
export const FLAKE8_EXTENSION = 'ms-python.flake8';
@@ -20,9 +20,11 @@ export class Flake8ExtensionPrompt implements IToolsExtensionPrompt {
2020
public constructor(private readonly serviceContainer: IServiceContainer) {}
2121

2222
public async showPrompt(): Promise<boolean> {
23-
if (isExtensionInstalled(this.serviceContainer, FLAKE8_EXTENSION)) {
23+
const isEnabled = isExtensionEnabled(this.serviceContainer, FLAKE8_EXTENSION);
24+
if (isEnabled || isExtensionDisabled(this.serviceContainer, FLAKE8_EXTENSION)) {
2425
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED, undefined, {
2526
extensionId: FLAKE8_EXTENSION,
27+
isEnabled,
2628
});
2729
return true;
2830
}

src/client/linters/prompts/pylintPrompt.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { showInformationMessage } from '../../common/vscodeApis/windowApis';
88
import { IServiceContainer } from '../../ioc/types';
99
import { sendTelemetryEvent } from '../../telemetry';
1010
import { EventName } from '../../telemetry/constants';
11-
import { doNotShowPromptState, inToolsExtensionsExperiment, isExtensionInstalled } from './common';
11+
import { doNotShowPromptState, inToolsExtensionsExperiment, isExtensionDisabled, isExtensionEnabled } from './common';
1212
import { IToolsExtensionPrompt } from './types';
1313

1414
export const PYLINT_EXTENSION = 'ms-python.pylint';
@@ -20,9 +20,11 @@ export class PylintExtensionPrompt implements IToolsExtensionPrompt {
2020
public constructor(private readonly serviceContainer: IServiceContainer) {}
2121

2222
public async showPrompt(): Promise<boolean> {
23-
if (isExtensionInstalled(this.serviceContainer, PYLINT_EXTENSION)) {
23+
const isEnabled = isExtensionEnabled(this.serviceContainer, PYLINT_EXTENSION);
24+
if (isEnabled || isExtensionDisabled(this.serviceContainer, PYLINT_EXTENSION)) {
2425
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED, undefined, {
2526
extensionId: PYLINT_EXTENSION,
27+
isEnabled,
2628
});
2729
return true;
2830
}

src/client/linters/pylint.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { Product } from '../common/types';
77
import { IServiceContainer } from '../ioc/types';
88
import { traceError, traceLog } from '../logging';
99
import { BaseLinter } from './baseLinter';
10+
import { isExtensionEnabled } from './prompts/common';
11+
import { PYLINT_EXTENSION } from './prompts/pylintPrompt';
1012
import { IToolsExtensionPrompt } from './prompts/types';
1113
import { ILintMessage } from './types';
1214

@@ -26,8 +28,12 @@ export class Pylint extends BaseLinter {
2628
}
2729

2830
protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
29-
if (await this.prompt.showPrompt()) {
30-
traceLog('LINTING: Skipping linting from Python extension, since Pylint extension is installed.');
31+
await this.prompt.showPrompt();
32+
33+
if (isExtensionEnabled(this.serviceContainer, PYLINT_EXTENSION)) {
34+
traceLog(
35+
'LINTING: Skipping linting from Python extension, since Pylint extension is installed and enabled.',
36+
);
3137
return [];
3238
}
3339

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { IApplicationEnvironment } from '../../common/application/types';
5+
import { IPersistentState, IPersistentStateFactory } from '../../common/types';
6+
import { Common, ToolsExtensions } from '../../common/utils/localize';
7+
import { executeCommand } from '../../common/vscodeApis/commandApis';
8+
import { isExtensionDisabled, isExtensionEnabled } from '../../common/vscodeApis/extensionsApi';
9+
import { showInformationMessage } from '../../common/vscodeApis/windowApis';
10+
import { IServiceContainer } from '../../ioc/types';
11+
import { sendTelemetryEvent } from '../../telemetry';
12+
import { EventName } from '../../telemetry/constants';
13+
14+
export const ISORT_EXTENSION = 'ms-python.isort';
15+
const ISORT_PROMPT_DONOTSHOW_KEY = 'showISortExtensionPrompt';
16+
17+
function doNotShowPromptState(serviceContainer: IServiceContainer, promptKey: string): IPersistentState<boolean> {
18+
const persistFactory: IPersistentStateFactory = serviceContainer.get<IPersistentStateFactory>(
19+
IPersistentStateFactory,
20+
);
21+
return persistFactory.createWorkspacePersistentState<boolean>(promptKey, false);
22+
}
23+
24+
export class ISortExtensionPrompt {
25+
private shownThisSession = false;
26+
27+
public constructor(private readonly serviceContainer: IServiceContainer) {}
28+
29+
public async showPrompt(): Promise<boolean> {
30+
const isEnabled = isExtensionEnabled(ISORT_EXTENSION);
31+
if (isEnabled || isExtensionDisabled(ISORT_EXTENSION)) {
32+
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED, undefined, {
33+
extensionId: ISORT_EXTENSION,
34+
isEnabled,
35+
});
36+
return true;
37+
}
38+
39+
const doNotShow = doNotShowPromptState(this.serviceContainer, ISORT_PROMPT_DONOTSHOW_KEY);
40+
if (this.shownThisSession || doNotShow.value) {
41+
return false;
42+
}
43+
44+
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_SHOWN, undefined, { extensionId: ISORT_EXTENSION });
45+
this.shownThisSession = true;
46+
const response = await showInformationMessage(
47+
ToolsExtensions.isortPromptMessage,
48+
ToolsExtensions.installISortExtension,
49+
Common.doNotShowAgain,
50+
);
51+
52+
if (response === Common.doNotShowAgain) {
53+
await doNotShow.updateValue(true);
54+
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED, undefined, {
55+
extensionId: ISORT_EXTENSION,
56+
dismissType: 'doNotShow',
57+
});
58+
return false;
59+
}
60+
61+
if (response === ToolsExtensions.installISortExtension) {
62+
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_INSTALL_SELECTED, undefined, {
63+
extensionId: ISORT_EXTENSION,
64+
});
65+
const appEnv: IApplicationEnvironment = this.serviceContainer.get<IApplicationEnvironment>(
66+
IApplicationEnvironment,
67+
);
68+
await executeCommand('workbench.extensions.installExtension', ISORT_EXTENSION, {
69+
installPreReleaseVersion: appEnv.extensionChannel === 'insiders',
70+
});
71+
return true;
72+
}
73+
74+
sendTelemetryEvent(EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED, undefined, {
75+
extensionId: ISORT_EXTENSION,
76+
dismissType: 'close',
77+
});
78+
79+
return false;
80+
}
81+
}
82+
83+
let _prompt: ISortExtensionPrompt | undefined;
84+
export function getOrCreateISortPrompt(serviceContainer: IServiceContainer): ISortExtensionPrompt {
85+
if (!_prompt) {
86+
_prompt = new ISortExtensionPrompt(serviceContainer);
87+
}
88+
return _prompt;
89+
}

src/client/providers/codeActionProvider/main.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@ import { IExtensionSingleActivationService } from '../../activation/types';
77
import { Commands } from '../../common/constants';
88
import { IDisposableRegistry } from '../../common/types';
99
import { executeCommand, registerCommand } from '../../common/vscodeApis/commandApis';
10+
import { isExtensionEnabled } from '../../common/vscodeApis/extensionsApi';
11+
import { IServiceContainer } from '../../ioc/types';
12+
import { traceLog } from '../../logging';
13+
import { getOrCreateISortPrompt, ISORT_EXTENSION } from './isortPrompt';
1014
import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider';
1115

1216
@injectable()
1317
export class CodeActionProviderService implements IExtensionSingleActivationService {
1418
public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false };
1519

16-
constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {}
20+
constructor(
21+
@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry,
22+
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
23+
) {}
1724

1825
public async activate(): Promise<void> {
1926
// eslint-disable-next-line global-require
@@ -29,7 +36,18 @@ export class CodeActionProviderService implements IExtensionSingleActivationServ
2936
}),
3037
);
3138
this.disposableRegistry.push(
32-
registerCommand(Commands.Sort_Imports, () => executeCommand('editor.action.organizeImports')),
39+
registerCommand(Commands.Sort_Imports, async () => {
40+
const prompt = getOrCreateISortPrompt(this.serviceContainer);
41+
await prompt.showPrompt();
42+
if (!isExtensionEnabled(ISORT_EXTENSION)) {
43+
traceLog(
44+
'Sort Imports: Please install and enable `ms-python.isort` extension to use this feature.',
45+
);
46+
return;
47+
}
48+
49+
executeCommand('editor.action.organizeImports');
50+
}),
3351
);
3452
}
3553
}

src/client/telemetry/index.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -2080,7 +2080,8 @@ export interface IEventNamePropertyMapping {
20802080
}
20812081
*/
20822082
[EventName.TOOLS_EXTENSIONS_ALREADY_INSTALLED]: {
2083-
extensionId: 'ms-python.pylint' | 'ms-python.flake8';
2083+
extensionId: 'ms-python.pylint' | 'ms-python.flake8' | 'ms-python.isort';
2084+
isEnabled: boolean;
20842085
};
20852086
/**
20862087
* Telemetry event sent when install linter or formatter extension prompt is shown.
@@ -2091,7 +2092,7 @@ export interface IEventNamePropertyMapping {
20912092
}
20922093
*/
20932094
[EventName.TOOLS_EXTENSIONS_PROMPT_SHOWN]: {
2094-
extensionId: 'ms-python.pylint' | 'ms-python.flake8';
2095+
extensionId: 'ms-python.pylint' | 'ms-python.flake8' | 'ms-python.isort';
20952096
};
20962097
/**
20972098
* Telemetry event sent when clicking to install linter or formatter extension from the suggestion prompt.
@@ -2102,7 +2103,7 @@ export interface IEventNamePropertyMapping {
21022103
}
21032104
*/
21042105
[EventName.TOOLS_EXTENSIONS_INSTALL_SELECTED]: {
2105-
extensionId: 'ms-python.pylint' | 'ms-python.flake8';
2106+
extensionId: 'ms-python.pylint' | 'ms-python.flake8' | 'ms-python.isort';
21062107
};
21072108
/**
21082109
* Telemetry event sent when dismissing prompt suggesting to install the linter or formatter extension.
@@ -2114,7 +2115,7 @@ export interface IEventNamePropertyMapping {
21142115
}
21152116
*/
21162117
[EventName.TOOLS_EXTENSIONS_PROMPT_DISMISSED]: {
2117-
extensionId: 'ms-python.pylint' | 'ms-python.flake8';
2118+
extensionId: 'ms-python.pylint' | 'ms-python.flake8' | 'ms-python.isort';
21182119
dismissType: 'close' | 'doNotShow';
21192120
};
21202121
/* __GDPR__

0 commit comments

Comments
 (0)