@@ -7,11 +7,12 @@ import { CancellationToken, OutputChannel, Uri } from 'vscode';
7
7
import '../extensions' ;
8
8
import { IInterpreterService } from '../../interpreter/contracts' ;
9
9
import { IServiceContainer } from '../../ioc/types' ;
10
+ import { LinterId } from '../../linters/types' ;
10
11
import { EnvironmentType , PythonEnvironment } from '../../pythonEnvironments/info' ;
11
12
import { sendTelemetryEvent } from '../../telemetry' ;
12
13
import { EventName } from '../../telemetry/constants' ;
13
- import { IApplicationShell , IWorkspaceService } from '../application/types' ;
14
- import { STANDARD_OUTPUT_CHANNEL } from '../constants' ;
14
+ import { IApplicationShell , ICommandManager , IWorkspaceService } from '../application/types' ;
15
+ import { Commands , STANDARD_OUTPUT_CHANNEL } from '../constants' ;
15
16
import { traceError , traceInfo } from '../logger' ;
16
17
import { IPlatformService } from '../platform/types' ;
17
18
import { IProcessServiceFactory , IPythonExecutionFactory } from '../process/types' ;
@@ -21,12 +22,13 @@ import {
21
22
IInstaller ,
22
23
InstallerResponse ,
23
24
IOutputChannel ,
25
+ IPersistentStateFactory ,
24
26
ProductInstallStatus ,
25
27
ModuleNamePurpose ,
26
28
Product ,
27
29
ProductType ,
28
30
} from '../types' ;
29
- import { Installer } from '../utils/localize' ;
31
+ import { Common , Installer , Linters } from '../utils/localize' ;
30
32
import { isResource , noop } from '../utils/misc' ;
31
33
import { translateProductToModule } from './moduleInstaller' ;
32
34
import { ProductNames } from './productNames' ;
@@ -306,7 +308,102 @@ export class FormatterInstaller extends BaseInstaller {
306
308
return InstallerResponse . Ignore ;
307
309
}
308
310
}
309
- class TestFrameworkInstaller extends BaseInstaller {
311
+
312
+ export class LinterInstaller extends BaseInstaller {
313
+ constructor ( protected serviceContainer : IServiceContainer , protected outputChannel : OutputChannel ) {
314
+ super ( serviceContainer , outputChannel ) ;
315
+ }
316
+
317
+ protected async promptToInstallImplementation (
318
+ product : Product ,
319
+ resource ?: Uri ,
320
+ cancel ?: CancellationToken ,
321
+ ) : Promise < InstallerResponse > {
322
+ sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT , undefined , {
323
+ prompt : 'old' ,
324
+ } ) ;
325
+
326
+ return this . oldPromptForInstallation ( product , resource , cancel ) ;
327
+ }
328
+
329
+ /**
330
+ * For installers that want to avoid prompting the user over and over, they can make use of a
331
+ * persisted true/false value representing user responses to 'stop showing this prompt'. This method
332
+ * gets the persisted value given the installer-defined key.
333
+ *
334
+ * @param key Key to use to get a persisted response value, each installer must define this for themselves.
335
+ * @returns Boolean: The current state of the stored response key given.
336
+ */
337
+ protected getStoredResponse ( key : string ) : boolean {
338
+ const factory = this . serviceContainer . get < IPersistentStateFactory > ( IPersistentStateFactory ) ;
339
+ const state = factory . createGlobalPersistentState < boolean | undefined > ( key , undefined ) ;
340
+ return state . value === true ;
341
+ }
342
+
343
+ private async oldPromptForInstallation ( product : Product , resource ?: Uri , cancel ?: CancellationToken ) {
344
+ const productName = ProductNames . get ( product ) ! ;
345
+ const install = Common . install ( ) ;
346
+ const doNotShowAgain = Common . doNotShowAgain ( ) ;
347
+ const disableLinterInstallPromptKey = `${ productName } _DisableLinterInstallPrompt` ;
348
+ const selectLinter = Linters . selectLinter ( ) ;
349
+
350
+ if ( this . getStoredResponse ( disableLinterInstallPromptKey ) === true ) {
351
+ return InstallerResponse . Ignore ;
352
+ }
353
+
354
+ const options = [ selectLinter , doNotShowAgain ] ;
355
+
356
+ let message = `Linter ${ productName } is not installed.` ;
357
+ if ( this . isExecutableAModule ( product , resource ) ) {
358
+ options . splice ( 0 , 0 , install ) ;
359
+ } else {
360
+ const executable = this . getExecutableNameFromSettings ( product , resource ) ;
361
+ message = `Path to the ${ productName } linter is invalid (${ executable } )` ;
362
+ }
363
+ const response = await this . appShell . showErrorMessage ( message , ...options ) ;
364
+ if ( response === install ) {
365
+ sendTelemetryEvent ( EventName . LINTER_NOT_INSTALLED_PROMPT , undefined , {
366
+ tool : productName as LinterId ,
367
+ action : 'install' ,
368
+ } ) ;
369
+ return this . install ( product , resource , cancel ) ;
370
+ }
371
+ if ( response === doNotShowAgain ) {
372
+ await this . setStoredResponse ( disableLinterInstallPromptKey , true ) ;
373
+ sendTelemetryEvent ( EventName . LINTER_NOT_INSTALLED_PROMPT , undefined , {
374
+ tool : productName as LinterId ,
375
+ action : 'disablePrompt' ,
376
+ } ) ;
377
+ return InstallerResponse . Ignore ;
378
+ }
379
+
380
+ if ( response === selectLinter ) {
381
+ sendTelemetryEvent ( EventName . LINTER_NOT_INSTALLED_PROMPT , undefined , { action : 'select' } ) ;
382
+ const commandManager = this . serviceContainer . get < ICommandManager > ( ICommandManager ) ;
383
+ await commandManager . executeCommand ( Commands . Set_Linter ) ;
384
+ }
385
+ return InstallerResponse . Ignore ;
386
+ }
387
+
388
+ /**
389
+ * For installers that want to avoid prompting the user over and over, they can make use of a
390
+ * persisted true/false value representing user responses to 'stop showing this prompt'. This
391
+ * method will set that persisted value given the installer-defined key.
392
+ *
393
+ * @param key Key to use to get a persisted response value, each installer must define this for themselves.
394
+ * @param value Boolean value to store for the user - if they choose to not be prompted again for instance.
395
+ * @returns Boolean: The current state of the stored response key given.
396
+ */
397
+ private async setStoredResponse ( key : string , value : boolean ) : Promise < void > {
398
+ const factory = this . serviceContainer . get < IPersistentStateFactory > ( IPersistentStateFactory ) ;
399
+ const state = factory . createGlobalPersistentState < boolean | undefined > ( key , undefined ) ;
400
+ if ( state && state . value !== value ) {
401
+ await state . updateValue ( value ) ;
402
+ }
403
+ }
404
+ }
405
+
406
+ export class TestFrameworkInstaller extends BaseInstaller {
310
407
protected async promptToInstallImplementation (
311
408
product : Product ,
312
409
resource ?: Uri ,
@@ -490,6 +587,8 @@ export class ProductInstaller implements IInstaller {
490
587
switch ( productType ) {
491
588
case ProductType . Formatter :
492
589
return new FormatterInstaller ( this . serviceContainer , this . outputChannel ) ;
590
+ case ProductType . Linter :
591
+ return new LinterInstaller ( this . serviceContainer , this . outputChannel ) ;
493
592
case ProductType . WorkspaceSymbols :
494
593
return new CTagsInstaller ( this . serviceContainer , this . outputChannel ) ;
495
594
case ProductType . TestFramework :
0 commit comments