@@ -2,11 +2,12 @@ import { inject, injectable, named } from 'inversify';
2
2
import * as os from 'os' ;
3
3
import * as path from 'path' ;
4
4
import * as vscode from 'vscode' ;
5
+ import '../../common/extensions' ;
5
6
import { IFormatterHelper } from '../../formatters/types' ;
6
7
import { IServiceContainer } from '../../ioc/types' ;
7
8
import { ILinterManager } from '../../linters/types' ;
8
9
import { ITestsHelper } from '../../unittests/common/types' ;
9
- import { IApplicationShell } from '../application/types' ;
10
+ import { IApplicationShell , IWorkspaceService } from '../application/types' ;
10
11
import { STANDARD_OUTPUT_CHANNEL } from '../constants' ;
11
12
import { IPlatformService } from '../platform/types' ;
12
13
import { IProcessServiceFactory , IPythonExecutionFactory } from '../process/types' ;
@@ -28,16 +29,34 @@ enum ProductType {
28
29
}
29
30
30
31
// tslint:disable-next-line:max-classes-per-file
31
- abstract class BaseInstaller {
32
+ export abstract class BaseInstaller {
33
+ private static readonly PromptPromises = new Map < string , Promise < InstallerResponse > > ( ) ;
32
34
protected appShell : IApplicationShell ;
33
35
protected configService : IConfigurationService ;
36
+ private readonly workspaceService : IWorkspaceService ;
34
37
35
38
constructor ( protected serviceContainer : IServiceContainer , protected outputChannel : vscode . OutputChannel ) {
36
39
this . appShell = serviceContainer . get < IApplicationShell > ( IApplicationShell ) ;
37
40
this . configService = serviceContainer . get < IConfigurationService > ( IConfigurationService ) ;
41
+ this . workspaceService = serviceContainer . get < IWorkspaceService > ( IWorkspaceService ) ;
38
42
}
39
43
40
- public abstract promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > ;
44
+ public promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
45
+ // If this method gets called twice, while previous promise has not been resolved, then return that same promise.
46
+ // E.g. previous promise is not resolved as a message has been displayed to the user, so no point displaying
47
+ // another message.
48
+ const workspaceFolder = resource ? this . workspaceService . getWorkspaceFolder ( resource ) : undefined ;
49
+ const key = `${ product } ${ workspaceFolder ? workspaceFolder . uri . fsPath : '' } ` ;
50
+ if ( BaseInstaller . PromptPromises . has ( key ) ) {
51
+ return BaseInstaller . PromptPromises . get ( key ) ! ;
52
+ }
53
+ const promise = this . promptToInstallImplementation ( product , resource ) ;
54
+ BaseInstaller . PromptPromises . set ( key , promise ) ;
55
+ promise . then ( ( ) => BaseInstaller . PromptPromises . delete ( key ) ) . ignoreErrors ( ) ;
56
+ promise . catch ( ( ) => BaseInstaller . PromptPromises . delete ( key ) ) . ignoreErrors ( ) ;
57
+
58
+ return promise ;
59
+ }
41
60
42
61
public async install ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
43
62
if ( product === Product . unittest ) {
@@ -83,22 +102,17 @@ abstract class BaseInstaller {
83
102
. catch ( ( ) => false ) ;
84
103
}
85
104
}
86
-
105
+ protected abstract promptToInstallImplementation ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > ;
87
106
protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
88
107
throw new Error ( 'getExecutableNameFromSettings is not supported on this object' ) ;
89
108
}
90
109
}
91
110
92
- class CTagsInstaller extends BaseInstaller {
111
+ export class CTagsInstaller extends BaseInstaller {
93
112
constructor ( serviceContainer : IServiceContainer , outputChannel : vscode . OutputChannel ) {
94
113
super ( serviceContainer , outputChannel ) ;
95
114
}
96
115
97
- public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
98
- const item = await this . appShell . showErrorMessage ( 'Install CTags to enable Python workspace symbols?' , 'Yes' , 'No' ) ;
99
- return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
100
- }
101
-
102
116
public async install ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
103
117
if ( this . serviceContainer . get < IPlatformService > ( IPlatformService ) . isWindows ) {
104
118
this . outputChannel . appendLine ( 'Install Universal Ctags Win32 to enable support for Workspace Symbols' ) ;
@@ -115,15 +129,19 @@ class CTagsInstaller extends BaseInstaller {
115
129
}
116
130
return InstallerResponse . Ignore ;
117
131
}
132
+ protected async promptToInstallImplementation ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
133
+ const item = await this . appShell . showErrorMessage ( 'Install CTags to enable Python workspace symbols?' , 'Yes' , 'No' ) ;
134
+ return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
135
+ }
118
136
119
137
protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
120
138
const settings = this . configService . getSettings ( resource ) ;
121
139
return settings . workspaceSymbols . ctagsPath ;
122
140
}
123
141
}
124
142
125
- class FormatterInstaller extends BaseInstaller {
126
- public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
143
+ export class FormatterInstaller extends BaseInstaller {
144
+ protected async promptToInstallImplementation ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
127
145
// Hard-coded on purpose because the UI won't necessarily work having
128
146
// another formatter.
129
147
const formatters = [ Product . autopep8 , Product . black , Product . yapf ] ;
@@ -159,8 +177,8 @@ class FormatterInstaller extends BaseInstaller {
159
177
}
160
178
161
179
// tslint:disable-next-line:max-classes-per-file
162
- class LinterInstaller extends BaseInstaller {
163
- public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
180
+ export class LinterInstaller extends BaseInstaller {
181
+ protected async promptToInstallImplementation ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
164
182
const productName = ProductNames . get ( product ) ! ;
165
183
const install = 'Install' ;
166
184
const disableAllLinting = 'Disable linting' ;
@@ -188,8 +206,8 @@ class LinterInstaller extends BaseInstaller {
188
206
}
189
207
190
208
// tslint:disable-next-line:max-classes-per-file
191
- class TestFrameworkInstaller extends BaseInstaller {
192
- public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
209
+ export class TestFrameworkInstaller extends BaseInstaller {
210
+ protected async promptToInstallImplementation ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
193
211
const productName = ProductNames . get ( product ) ! ;
194
212
const item = await this . appShell . showErrorMessage ( `Test framework ${ productName } is not installed. Install?` , 'Yes' , 'No' ) ;
195
213
return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
@@ -208,8 +226,8 @@ class TestFrameworkInstaller extends BaseInstaller {
208
226
}
209
227
210
228
// tslint:disable-next-line:max-classes-per-file
211
- class RefactoringLibraryInstaller extends BaseInstaller {
212
- public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
229
+ export class RefactoringLibraryInstaller extends BaseInstaller {
230
+ protected async promptToInstallImplementation ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
213
231
const productName = ProductNames . get ( product ) ! ;
214
232
const item = await this . appShell . showErrorMessage ( `Refactoring library ${ productName } is not installed. Install?` , 'Yes' , 'No' ) ;
215
233
return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
0 commit comments