11import { inject , injectable , named } from 'inversify' ;
22import * as os from 'os' ;
33import * as path from 'path' ;
4- import { OutputChannel , Uri } from 'vscode' ;
54import * as vscode from 'vscode' ;
65import { IFormatterHelper } from '../../formatters/types' ;
76import { IServiceContainer } from '../../ioc/types' ;
@@ -33,14 +32,14 @@ abstract class BaseInstaller {
3332 protected appShell : IApplicationShell ;
3433 protected configService : IConfigurationService ;
3534
36- constructor ( protected serviceContainer : IServiceContainer , protected outputChannel : OutputChannel ) {
35+ constructor ( protected serviceContainer : IServiceContainer , protected outputChannel : vscode . OutputChannel ) {
3736 this . appShell = serviceContainer . get < IApplicationShell > ( IApplicationShell ) ;
3837 this . configService = serviceContainer . get < IConfigurationService > ( IConfigurationService ) ;
3938 }
4039
41- public abstract promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > ;
40+ public abstract promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > ;
4241
43- public async install ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
42+ public async install ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
4443 if ( product === Product . unittest ) {
4544 return InstallerResponse . Installed ;
4645 }
@@ -60,7 +59,7 @@ abstract class BaseInstaller {
6059 . then ( isInstalled => isInstalled ? InstallerResponse . Installed : InstallerResponse . Ignore ) ;
6160 }
6261
63- public async isInstalled ( product : Product , resource ?: Uri ) : Promise < boolean | undefined > {
62+ public async isInstalled ( product : Product , resource ?: vscode . Uri ) : Promise < boolean | undefined > {
6463 if ( product === Product . unittest ) {
6564 return true ;
6665 }
@@ -85,22 +84,22 @@ abstract class BaseInstaller {
8584 }
8685 }
8786
88- protected getExecutableNameFromSettings ( product : Product , resource ?: Uri ) : string {
87+ protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
8988 throw new Error ( 'getExecutableNameFromSettings is not supported on this object' ) ;
9089 }
9190}
9291
9392class CTagsInstaller extends BaseInstaller {
94- constructor ( serviceContainer : IServiceContainer , outputChannel : OutputChannel ) {
93+ constructor ( serviceContainer : IServiceContainer , outputChannel : vscode . OutputChannel ) {
9594 super ( serviceContainer , outputChannel ) ;
9695 }
9796
98- public async promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
97+ public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
9998 const item = await this . appShell . showErrorMessage ( 'Install CTags to enable Python workspace symbols?' , 'Yes' , 'No' ) ;
10099 return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
101100 }
102101
103- public async install ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
102+ public async install ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
104103 if ( this . serviceContainer . get < IPlatformService > ( IPlatformService ) . isWindows ) {
105104 this . outputChannel . appendLine ( 'Install Universal Ctags Win32 to enable support for Workspace Symbols' ) ;
106105 this . outputChannel . appendLine ( 'Download the CTags binary from the Universal CTags site.' ) ;
@@ -117,32 +116,41 @@ class CTagsInstaller extends BaseInstaller {
117116 return InstallerResponse . Ignore ;
118117 }
119118
120- protected getExecutableNameFromSettings ( product : Product , resource ?: Uri ) : string {
119+ protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
121120 const settings = this . configService . getSettings ( resource ) ;
122121 return settings . workspaceSymbols . ctagsPath ;
123122 }
124123}
125124
126125class FormatterInstaller extends BaseInstaller {
127- public async promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
126+ public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
127+ // Hard-coded on purpose because the UI won't necessarily work having
128+ // another formatter.
129+ const formatters = [ Product . autopep8 , Product . black , Product . yapf ] ;
130+ const formatterNames = formatters . map ( ( formatter ) => ProductNames . get ( formatter ) ! ) ;
128131 const productName = ProductNames . get ( product ) ! ;
132+ formatterNames . splice ( formatterNames . indexOf ( productName ) , 1 ) ;
133+ const useOptions = formatterNames . map ( ( name ) => `Use ${ name } ` ) ;
134+ const yesChoice = 'Yes' ;
129135
130- const installThis = `Install ${ productName } ` ;
131- const alternateFormatter = product === Product . autopep8 ? 'yapf' : 'autopep8' ;
132- const useOtherFormatter = `Use '${ alternateFormatter } ' formatter` ;
133- const item = await this . appShell . showErrorMessage ( `Formatter ${ productName } is not installed.` , installThis , useOtherFormatter ) ;
134-
135- if ( item === installThis ) {
136+ const item = await this . appShell . showErrorMessage ( `Formatter ${ productName } is not installed. Install?` , yesChoice , ...useOptions ) ;
137+ if ( item === yesChoice ) {
136138 return this . install ( product , resource ) ;
139+ } else if ( typeof item === 'string' ) {
140+ for ( const formatter of formatters ) {
141+ const formatterName = ProductNames . get ( formatter ) ! ;
142+
143+ if ( item . endsWith ( formatterName ) ) {
144+ await this . configService . updateSettingAsync ( 'formatting.provider' , formatterName , resource ) ;
145+ return this . install ( formatter , resource ) ;
146+ }
147+ }
137148 }
138- if ( item === useOtherFormatter ) {
139- await this . configService . updateSettingAsync ( 'formatting.provider' , alternateFormatter , resource ) ;
140- return InstallerResponse . Installed ;
141- }
149+
142150 return InstallerResponse . Ignore ;
143151 }
144152
145- protected getExecutableNameFromSettings ( product : Product , resource ?: Uri ) : string {
153+ protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
146154 const settings = this . configService . getSettings ( resource ) ;
147155 const formatHelper = this . serviceContainer . get < IFormatterHelper > ( IFormatterHelper ) ;
148156 const settingsPropNames = formatHelper . getSettingsPropertyNames ( product ) ;
@@ -152,7 +160,7 @@ class FormatterInstaller extends BaseInstaller {
152160
153161// tslint:disable-next-line:max-classes-per-file
154162class LinterInstaller extends BaseInstaller {
155- public async promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
163+ public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
156164 const productName = ProductNames . get ( product ) ! ;
157165 const install = 'Install' ;
158166 const disableAllLinting = 'Disable linting' ;
@@ -173,21 +181,21 @@ class LinterInstaller extends BaseInstaller {
173181 }
174182 return InstallerResponse . Ignore ;
175183 }
176- protected getExecutableNameFromSettings ( product : Product , resource ?: Uri ) : string {
184+ protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
177185 const linterManager = this . serviceContainer . get < ILinterManager > ( ILinterManager ) ;
178186 return linterManager . getLinterInfo ( product ) . pathName ( resource ) ;
179187 }
180188}
181189
182190// tslint:disable-next-line:max-classes-per-file
183191class TestFrameworkInstaller extends BaseInstaller {
184- public async promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
192+ public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
185193 const productName = ProductNames . get ( product ) ! ;
186194 const item = await this . appShell . showErrorMessage ( `Test framework ${ productName } is not installed. Install?` , 'Yes' , 'No' ) ;
187195 return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
188196 }
189197
190- protected getExecutableNameFromSettings ( product : Product , resource ?: Uri ) : string {
198+ protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
191199 const testHelper = this . serviceContainer . get < ITestsHelper > ( ITestsHelper ) ;
192200 const settingsPropNames = testHelper . getSettingsPropertyNames ( product ) ;
193201 if ( ! settingsPropNames . pathName ) {
@@ -201,12 +209,12 @@ class TestFrameworkInstaller extends BaseInstaller {
201209
202210// tslint:disable-next-line:max-classes-per-file
203211class RefactoringLibraryInstaller extends BaseInstaller {
204- public async promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
212+ public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
205213 const productName = ProductNames . get ( product ) ! ;
206214 const item = await this . appShell . showErrorMessage ( `Refactoring library ${ productName } is not installed. Install?` , 'Yes' , 'No' ) ;
207215 return item === 'Yes' ? this . install ( product , resource ) : InstallerResponse . Ignore ;
208216 }
209- protected getExecutableNameFromSettings ( product : Product , resource ?: Uri ) : string {
217+ protected getExecutableNameFromSettings ( product : Product , resource ?: vscode . Uri ) : string {
210218 return translateProductToModule ( product , ModuleNamePurpose . run ) ;
211219 }
212220}
@@ -230,19 +238,20 @@ export class ProductInstaller implements IInstaller {
230238 this . ProductTypes . set ( Product . pytest , ProductType . TestFramework ) ;
231239 this . ProductTypes . set ( Product . unittest , ProductType . TestFramework ) ;
232240 this . ProductTypes . set ( Product . autopep8 , ProductType . Formatter ) ;
241+ this . ProductTypes . set ( Product . black , ProductType . Formatter ) ;
233242 this . ProductTypes . set ( Product . yapf , ProductType . Formatter ) ;
234243 this . ProductTypes . set ( Product . rope , ProductType . RefactoringLibrary ) ;
235244 }
236245
237246 // tslint:disable-next-line:no-empty
238247 public dispose ( ) { }
239- public async promptToInstall ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
248+ public async promptToInstall ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
240249 return this . createInstaller ( product ) . promptToInstall ( product , resource ) ;
241250 }
242- public async install ( product : Product , resource ?: Uri ) : Promise < InstallerResponse > {
251+ public async install ( product : Product , resource ?: vscode . Uri ) : Promise < InstallerResponse > {
243252 return this . createInstaller ( product ) . install ( product , resource ) ;
244253 }
245- public async isInstalled ( product : Product , resource ?: Uri ) : Promise < boolean | undefined > {
254+ public async isInstalled ( product : Product , resource ?: vscode . Uri ) : Promise < boolean | undefined > {
246255 return this . createInstaller ( product ) . isInstalled ( product , resource ) ;
247256 }
248257 public translateProductToModuleName ( product : Product , purpose : ModuleNamePurpose ) : string {
@@ -280,6 +289,7 @@ function translateProductToModule(product: Product, purpose: ModuleNamePurpose):
280289 case Product . pylint : return 'pylint' ;
281290 case Product . pytest : return 'pytest' ;
282291 case Product . autopep8 : return 'autopep8' ;
292+ case Product . black : return 'black' ;
283293 case Product . pep8 : return 'pep8' ;
284294 case Product . pydocstyle : return 'pydocstyle' ;
285295 case Product . yapf : return 'yapf' ;
0 commit comments