forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Show prompt asking user to install formatter extension (#20861)
For #19653
- Loading branch information
1 parent
b9c4ff7
commit 7ee3f7d
Showing
11 changed files
with
574 additions
and
31 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,45 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { | ||
CancellationToken, | ||
ConfigurationScope, | ||
GlobPattern, | ||
Uri, | ||
workspace, | ||
WorkspaceConfiguration, | ||
WorkspaceEdit, | ||
WorkspaceFolder, | ||
} from 'vscode'; | ||
import * as vscode from 'vscode'; | ||
import { Resource } from '../types'; | ||
|
||
export function getWorkspaceFolders(): readonly WorkspaceFolder[] | undefined { | ||
return workspace.workspaceFolders; | ||
export function getWorkspaceFolders(): readonly vscode.WorkspaceFolder[] | undefined { | ||
return vscode.workspace.workspaceFolders; | ||
} | ||
|
||
export function getWorkspaceFolder(uri: Resource): WorkspaceFolder | undefined { | ||
return uri ? workspace.getWorkspaceFolder(uri) : undefined; | ||
export function getWorkspaceFolder(uri: Resource): vscode.WorkspaceFolder | undefined { | ||
return uri ? vscode.workspace.getWorkspaceFolder(uri) : undefined; | ||
} | ||
|
||
export function getWorkspaceFolderPaths(): string[] { | ||
return workspace.workspaceFolders?.map((w) => w.uri.fsPath) ?? []; | ||
return vscode.workspace.workspaceFolders?.map((w) => w.uri.fsPath) ?? []; | ||
} | ||
|
||
export function getConfiguration(section?: string, scope?: ConfigurationScope | null): WorkspaceConfiguration { | ||
return workspace.getConfiguration(section, scope); | ||
export function getConfiguration( | ||
section?: string, | ||
scope?: vscode.ConfigurationScope | null, | ||
): vscode.WorkspaceConfiguration { | ||
return vscode.workspace.getConfiguration(section, scope); | ||
} | ||
|
||
export function applyEdit(edit: WorkspaceEdit): Thenable<boolean> { | ||
return workspace.applyEdit(edit); | ||
export function applyEdit(edit: vscode.WorkspaceEdit): Thenable<boolean> { | ||
return vscode.workspace.applyEdit(edit); | ||
} | ||
|
||
export function findFiles( | ||
include: GlobPattern, | ||
exclude?: GlobPattern | null, | ||
include: vscode.GlobPattern, | ||
exclude?: vscode.GlobPattern | null, | ||
maxResults?: number, | ||
token?: CancellationToken, | ||
): Thenable<Uri[]> { | ||
return workspace.findFiles(include, exclude, maxResults, token); | ||
token?: vscode.CancellationToken, | ||
): Thenable<vscode.Uri[]> { | ||
return vscode.workspace.findFiles(include, exclude, maxResults, token); | ||
} | ||
|
||
export function onDidSaveTextDocument( | ||
listener: (e: vscode.TextDocument) => unknown, | ||
thisArgs?: unknown, | ||
disposables?: vscode.Disposable[], | ||
): vscode.Disposable { | ||
return vscode.workspace.onDidSaveTextDocument(listener, thisArgs, disposables); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { Uri } from 'vscode'; | ||
import { IDisposableRegistry } from '../../common/types'; | ||
import { Common, ToolsExtensions } from '../../common/utils/localize'; | ||
import { isExtensionEnabled } from '../../common/vscodeApis/extensionsApi'; | ||
import { showInformationMessage } from '../../common/vscodeApis/windowApis'; | ||
import { getConfiguration, onDidSaveTextDocument } from '../../common/vscodeApis/workspaceApis'; | ||
import { IServiceContainer } from '../../ioc/types'; | ||
import { | ||
doNotShowPromptState, | ||
inFormatterExtensionExperiment, | ||
installFormatterExtension, | ||
updateDefaultFormatter, | ||
} from './promptUtils'; | ||
import { AUTOPEP8_EXTENSION, BLACK_EXTENSION, IInstallFormatterPrompt } from './types'; | ||
|
||
const SHOW_FORMATTER_INSTALL_PROMPT_DONOTSHOW_KEY = 'showFormatterExtensionInstallPrompt'; | ||
|
||
export class InstallFormatterPrompt implements IInstallFormatterPrompt { | ||
private shownThisSession = false; | ||
|
||
constructor(private readonly serviceContainer: IServiceContainer) {} | ||
|
||
public async showInstallFormatterPrompt(resource?: Uri): Promise<void> { | ||
if (!inFormatterExtensionExperiment(this.serviceContainer)) { | ||
return; | ||
} | ||
|
||
const promptState = doNotShowPromptState(SHOW_FORMATTER_INSTALL_PROMPT_DONOTSHOW_KEY, this.serviceContainer); | ||
if (this.shownThisSession || promptState.value) { | ||
return; | ||
} | ||
|
||
const config = getConfiguration('python', resource); | ||
const formatter = config.get<string>('formatting.provider', 'none'); | ||
if (!['autopep8', 'black'].includes(formatter)) { | ||
return; | ||
} | ||
|
||
const editorConfig = getConfiguration('editor', { uri: resource, languageId: 'python' }); | ||
const defaultFormatter = editorConfig.get<string>('defaultFormatter', ''); | ||
if ([BLACK_EXTENSION, AUTOPEP8_EXTENSION].includes(defaultFormatter)) { | ||
return; | ||
} | ||
|
||
const black = isExtensionEnabled(BLACK_EXTENSION); | ||
const autopep8 = isExtensionEnabled(AUTOPEP8_EXTENSION); | ||
|
||
let selection: string | undefined; | ||
|
||
if (black || autopep8) { | ||
this.shownThisSession = true; | ||
if (black && autopep8) { | ||
selection = await showInformationMessage( | ||
ToolsExtensions.selectMultipleFormattersPrompt, | ||
'Black', | ||
'Autopep8', | ||
Common.doNotShowAgain, | ||
); | ||
} else if (black) { | ||
selection = await showInformationMessage( | ||
ToolsExtensions.selectBlackFormatterPrompt, | ||
Common.bannerLabelYes, | ||
Common.doNotShowAgain, | ||
); | ||
if (selection === Common.bannerLabelYes) { | ||
selection = 'Black'; | ||
} | ||
} else if (autopep8) { | ||
selection = await showInformationMessage( | ||
ToolsExtensions.selectAutopep8FormatterPrompt, | ||
Common.bannerLabelYes, | ||
Common.doNotShowAgain, | ||
); | ||
if (selection === Common.bannerLabelYes) { | ||
selection = 'Autopep8'; | ||
} | ||
} | ||
} else if (formatter === 'black' && !black) { | ||
this.shownThisSession = true; | ||
selection = await showInformationMessage( | ||
ToolsExtensions.installBlackFormatterPrompt, | ||
'Black', | ||
'Autopep8', | ||
Common.doNotShowAgain, | ||
); | ||
} else if (formatter === 'autopep8' && !autopep8) { | ||
this.shownThisSession = true; | ||
selection = await showInformationMessage( | ||
ToolsExtensions.installAutopep8FormatterPrompt, | ||
'Black', | ||
'Autopep8', | ||
Common.doNotShowAgain, | ||
); | ||
} | ||
|
||
if (selection === 'Black') { | ||
if (black) { | ||
await updateDefaultFormatter(BLACK_EXTENSION, resource); | ||
} else { | ||
await installFormatterExtension(BLACK_EXTENSION, resource); | ||
} | ||
} else if (selection === 'Autopep8') { | ||
if (autopep8) { | ||
await updateDefaultFormatter(AUTOPEP8_EXTENSION, resource); | ||
} else { | ||
await installFormatterExtension(AUTOPEP8_EXTENSION, resource); | ||
} | ||
} else if (selection === Common.doNotShowAgain) { | ||
await promptState.updateValue(true); | ||
} | ||
} | ||
} | ||
|
||
export function registerInstallFormatterPrompt(serviceContainer: IServiceContainer): void { | ||
const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry); | ||
const installFormatterPrompt = new InstallFormatterPrompt(serviceContainer); | ||
disposables.push( | ||
onDidSaveTextDocument(async (e) => { | ||
const editorConfig = getConfiguration('editor', { uri: e.uri, languageId: 'python' }); | ||
if (e.languageId === 'python' && editorConfig.get<boolean>('formatOnSave')) { | ||
await installFormatterPrompt.showInstallFormatterPrompt(e.uri); | ||
} | ||
}), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { ConfigurationTarget, Uri } from 'vscode'; | ||
import { ShowFormatterExtensionPrompt } from '../../common/experiments/groups'; | ||
import { IExperimentService, IPersistentState, IPersistentStateFactory } from '../../common/types'; | ||
import { executeCommand } from '../../common/vscodeApis/commandApis'; | ||
import { isInsider } from '../../common/vscodeApis/extensionsApi'; | ||
import { getConfiguration, getWorkspaceFolder } from '../../common/vscodeApis/workspaceApis'; | ||
import { IServiceContainer } from '../../ioc/types'; | ||
|
||
export function inFormatterExtensionExperiment(serviceContainer: IServiceContainer): boolean { | ||
const experiment = serviceContainer.get<IExperimentService>(IExperimentService); | ||
return experiment.inExperimentSync(ShowFormatterExtensionPrompt.experiment); | ||
} | ||
|
||
export function doNotShowPromptState(key: string, serviceContainer: IServiceContainer): IPersistentState<boolean> { | ||
const persistFactory = serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory); | ||
const promptState = persistFactory.createWorkspacePersistentState<boolean>(key, false); | ||
return promptState; | ||
} | ||
|
||
export async function updateDefaultFormatter(extensionId: string, resource?: Uri): Promise<void> { | ||
const scope = getWorkspaceFolder(resource) ? ConfigurationTarget.Workspace : ConfigurationTarget.Global; | ||
|
||
const config = getConfiguration('python', resource); | ||
const editorConfig = getConfiguration('editor', { uri: resource, languageId: 'python' }); | ||
await editorConfig.update('defaultFormatter', extensionId, scope, true); | ||
await config.update('formatting.provider', 'none', scope); | ||
} | ||
|
||
export async function installFormatterExtension(extensionId: string, resource?: Uri): Promise<void> { | ||
await executeCommand('workbench.extensions.installExtension', extensionId, { | ||
installPreReleaseVersion: isInsider(), | ||
}); | ||
|
||
await updateDefaultFormatter(extensionId, resource); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
export const BLACK_EXTENSION = 'ms-python.black-formatter'; | ||
export const AUTOPEP8_EXTENSION = 'ms-python.autopep8'; | ||
|
||
export interface IInstallFormatterPrompt { | ||
showInstallFormatterPrompt(): Promise<void>; | ||
} |
Oops, something went wrong.