Skip to content

Commit 403c984

Browse files
authored
Allow users to not show 'Install missing Linter' prompt. (#3553)
1 parent 14286f9 commit 403c984

File tree

5 files changed

+192
-11
lines changed

5 files changed

+192
-11
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,4 @@
166166
]
167167
}
168168
]
169-
}
169+
}

news/1 Enhancements/3349.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow users to request the 'Install missing Linter' prompt to not show again for pylint.

src/client/common/installer/productInstaller.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import { STANDARD_OUTPUT_CHANNEL } from '../constants';
1111
import { IPlatformService } from '../platform/types';
1212
import { IProcessServiceFactory, IPythonExecutionFactory } from '../process/types';
1313
import { ITerminalServiceFactory } from '../terminal/types';
14-
import { IConfigurationService, IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product, ProductType } from '../types';
14+
import {
15+
IConfigurationService, IInstaller, ILogger, InstallerResponse, IOutputChannel,
16+
IPersistentStateFactory, ModuleNamePurpose, Product, ProductType
17+
} from '../types';
1518
import { ProductNames } from './productNames';
1619
import { IInstallationChannelManager, IProductPathService, IProductService } from './types';
1720

@@ -88,6 +91,7 @@ export abstract class BaseInstaller {
8891
.catch(() => false);
8992
}
9093
}
94+
9195
protected abstract promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse>;
9296
protected getExecutableNameFromSettings(product: Product, resource?: Uri): string {
9397
const productType = this.productService.getProductType(product);
@@ -168,12 +172,21 @@ export class FormatterInstaller extends BaseInstaller {
168172

169173
export class LinterInstaller extends BaseInstaller {
170174
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
175+
const isPylint = product === Product.pylint;
176+
171177
const productName = ProductNames.get(product)!;
172178
const install = 'Install';
173179
const disableAllLinting = 'Disable linting';
174180
const disableThisLinter = `Disable ${productName}`;
181+
const disableInstallPrompt = 'Do not show again';
182+
const disableLinterInstallPromptKey = `${productName}_DisableLinterInstallPrompt`;
183+
184+
if (isPylint && this.getStoredResponse(disableLinterInstallPromptKey) === true) {
185+
return InstallerResponse.Ignore;
186+
}
187+
188+
const options = isPylint ? [disableThisLinter, disableAllLinting, disableInstallPrompt] : [disableThisLinter, disableAllLinting];
175189

176-
const options = [disableThisLinter, disableAllLinting];
177190
let message = `Linter ${productName} is not installed.`;
178191
if (this.isExecutableAModule(product, resource)) {
179192
options.splice(0, 0, install);
@@ -185,7 +198,11 @@ export class LinterInstaller extends BaseInstaller {
185198
const response = await this.appShell.showErrorMessage(message, ...options);
186199
if (response === install) {
187200
return this.install(product, resource);
201+
} else if (response === disableInstallPrompt) {
202+
await this.setStoredResponse(disableLinterInstallPromptKey, true);
203+
return InstallerResponse.Ignore;
188204
}
205+
189206
const lm = this.serviceContainer.get<ILinterManager>(ILinterManager);
190207
if (response === disableAllLinting) {
191208
await lm.enableLintingAsync(false);
@@ -196,6 +213,37 @@ export class LinterInstaller extends BaseInstaller {
196213
}
197214
return InstallerResponse.Ignore;
198215
}
216+
217+
/**
218+
* For installers that want to avoid prompting the user over and over, they can make use of a
219+
* persisted true/false value representing user responses to 'stop showing this prompt'. This method
220+
* gets the persisted value given the installer-defined key.
221+
*
222+
* @param key Key to use to get a persisted response value, each installer must define this for themselves.
223+
* @returns Boolean: The current state of the stored response key given.
224+
*/
225+
private getStoredResponse(key: string): boolean {
226+
const factory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
227+
const state = factory.createGlobalPersistentState<boolean | undefined>(key, undefined);
228+
return state.value;
229+
}
230+
231+
/**
232+
* For installers that want to avoid prompting the user over and over, they can make use of a
233+
* persisted true/false value representing user responses to 'stop showing this prompt'. This
234+
* method will set that persisted value given the installer-defined key.
235+
*
236+
* @param key Key to use to get a persisted response value, each installer must define this for themselves.
237+
* @param value Boolean value to store for the user - if they choose to not be prompted again for instance.
238+
* @returns Boolean: The current state of the stored response key given.
239+
*/
240+
private async setStoredResponse(key: string, value: boolean): Promise<void> {
241+
const factory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
242+
const state = factory.createGlobalPersistentState<boolean | undefined>(key, undefined);
243+
if (state && state.value !== value) {
244+
await state.updateValue(value);
245+
}
246+
}
199247
}
200248

201249
export class TestFrameworkInstaller extends BaseInstaller {

src/test/common/installer/installer.invalidPath.unit.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import '../../../client/common/extensions';
1313
import { ProductInstaller } from '../../../client/common/installer/productInstaller';
1414
import { ProductService } from '../../../client/common/installer/productService';
1515
import { IProductPathService, IProductService } from '../../../client/common/installer/types';
16-
import { Product } from '../../../client/common/types';
16+
import { IPersistentState, IPersistentStateFactory, Product } from '../../../client/common/types';
1717
import { getNamesAndValues } from '../../../client/common/utils/enum';
1818
import { IServiceContainer } from '../../../client/ioc/types';
1919

@@ -30,6 +30,8 @@ suite('Module Installer - Invalid Paths', () => {
3030
let app: TypeMoq.IMock<IApplicationShell>;
3131
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
3232
let productPathService: TypeMoq.IMock<IProductPathService>;
33+
let persistentState: TypeMoq.IMock<IPersistentStateFactory>;
34+
3335
setup(() => {
3436
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
3537
const outputChannel = TypeMoq.Mock.ofType<OutputChannel>();
@@ -43,6 +45,9 @@ suite('Module Installer - Invalid Paths', () => {
4345
productPathService = TypeMoq.Mock.ofType<IProductPathService>();
4446
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProductPathService), TypeMoq.It.isAny())).returns(() => productPathService.object);
4547

48+
persistentState = TypeMoq.Mock.ofType<IPersistentStateFactory>();
49+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory), TypeMoq.It.isAny())).returns(() => persistentState.object);
50+
4651
installer = new ProductInstaller(serviceContainer.object, outputChannel.object);
4752
});
4853

@@ -74,7 +79,12 @@ suite('Module Installer - Invalid Paths', () => {
7479
})
7580
.returns(() => Promise.resolve(undefined))
7681
.verifiable(TypeMoq.Times.exactly(1));
77-
82+
const persistValue = TypeMoq.Mock.ofType<IPersistentState<boolean>>();
83+
persistValue.setup(pv => pv.value).returns(() => false);
84+
persistValue.setup(pv => pv.updateValue(TypeMoq.It.isValue(true)));
85+
persistentState.setup(ps =>
86+
ps.createGlobalPersistentState(TypeMoq.It.isAnyString(), TypeMoq.It.isValue(undefined))
87+
).returns(() => persistValue.object);
7888
await installer.promptToInstall(product.value, resource);
7989
productPathService.verifyAll();
8090
});

0 commit comments

Comments
 (0)