66import { ICodeEditor } from 'vs/editor/browser/editorBrowser' ;
77import { EditorAction , registerEditorAction , ServicesAccessor } from 'vs/editor/browser/editorExtensions' ;
88import { EditorContextKeys } from 'vs/editor/common/editorContextKeys' ;
9- import { DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes' ;
9+ import { DocumentRangeFormattingEditProviderRegistry , DocumentFormattingEditProvider , DocumentRangeFormattingEditProvider } from 'vs/editor/common/modes' ;
1010import * as nls from 'vs/nls' ;
1111import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey' ;
1212import { IQuickInputService , IQuickPickItem , IQuickInputButton } from 'vs/platform/quickinput/common/quickInput' ;
1313import { CancellationToken } from 'vs/base/common/cancellation' ;
1414import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
15- import { formatDocumentRangeWithProvider , formatDocumentWithProvider , getRealAndSyntheticDocumentFormattersOrdered } from 'vs/editor/contrib/format/format' ;
15+ import { formatDocumentRangeWithProvider , formatDocumentWithProvider , getRealAndSyntheticDocumentFormattersOrdered , FormattingConflicts , FormattingMode } from 'vs/editor/contrib/format/format' ;
1616import { Range } from 'vs/editor/common/core/range' ;
17- import { showExtensionQuery } from 'vs/workbench/contrib/format/browser/showExtensionQuery' ;
18- import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet' ;
1917import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
2018import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions' ;
19+ import { Registry } from 'vs/platform/registry/common/platform' ;
20+ import { IConfigurationRegistry , Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry' ;
21+ import { Extensions as WorkbenchExtensions , IWorkbenchContributionsRegistry , IWorkbenchContribution } from 'vs/workbench/common/contributions' ;
22+ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle' ;
23+ import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' ;
24+ import { Disposable } from 'vs/base/common/lifecycle' ;
25+ import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
26+ import { ITextModel } from 'vs/editor/common/model' ;
27+ import { INotificationService , Severity } from 'vs/platform/notification/common/notification' ;
28+ import { IModeService } from 'vs/editor/common/services/modeService' ;
29+
30+ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
31+
32+ static configName = 'editor.defaultFormatter' ;
33+
34+ static extensionIds : string [ ] = [ ] ;
35+ static extensionDescriptions : string [ ] = [ ] ;
36+
37+ constructor (
38+ @IExtensionService private readonly _extensionService : IExtensionService ,
39+ @IConfigurationService private readonly _configService : IConfigurationService ,
40+ @INotificationService private readonly _notificationService : INotificationService ,
41+ @IQuickInputService private readonly _quickInputService : IQuickInputService ,
42+ @IModeService private readonly _modeService : IModeService ,
43+ ) {
44+ super ( ) ;
45+ this . _register ( this . _extensionService . onDidChangeExtensions ( this . _updateConfigValues , this ) ) ;
46+ this . _register ( FormattingConflicts . setFormatterSelector ( ( formatter , document , mode ) => this . _selectFormatter ( formatter , document , mode ) ) ) ;
47+ this . _updateConfigValues ( ) ;
48+ }
49+
50+ private async _updateConfigValues ( ) : Promise < void > {
51+ const extensions = await this . _extensionService . getExtensions ( ) ;
52+
53+ DefaultFormatter . extensionIds . length = 0 ;
54+ DefaultFormatter . extensionDescriptions . length = 0 ;
55+ for ( const extension of extensions ) {
56+ DefaultFormatter . extensionIds . push ( extension . identifier . value ) ;
57+ DefaultFormatter . extensionDescriptions . push ( extension . description || '' ) ;
58+ }
59+ }
60+
61+ private static _maybeQuotes ( s : string ) : string {
62+ return s . match ( / \s / ) ? `'${ s } '` : s ;
63+ }
64+
65+ private async _selectFormatter < T extends DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider > ( formatter : T [ ] , document : ITextModel , mode : FormattingMode ) : Promise < T | undefined > {
66+
67+ const defaultFormatterId = this . _configService . getValue < string > ( DefaultFormatter . configName , {
68+ resource : document . uri ,
69+ overrideIdentifier : document . getModeId ( )
70+ } ) ;
71+
72+ if ( defaultFormatterId ) {
73+ // good -> formatter configured
74+ const [ defaultFormatter ] = formatter . filter ( formatter => formatter . extensionId && ExtensionIdentifier . equals ( formatter . extensionId , defaultFormatterId ) ) ;
75+ if ( defaultFormatter ) {
76+ // good -> formatter configured and available
77+ return defaultFormatter ;
78+ }
79+ }
80+
81+ const langName = this . _modeService . getLanguageName ( document . getModeId ( ) ) || document . getModeId ( ) ;
82+ const message = defaultFormatterId
83+ ? nls . localize ( 'config.bad' , "The configured default formatter is not available. Select a different default formatter to continue." )
84+ : nls . localize ( 'config.needed' , "There are multiple formatters for {0}-files. Select a default formatter to continue." , DefaultFormatter . _maybeQuotes ( langName ) ) ;
85+
86+ return new Promise < T | undefined > ( ( resolve , reject ) => {
87+ this . _notificationService . prompt (
88+ Severity . Info ,
89+ message ,
90+ [ { label : nls . localize ( 'do.config' , "Configure..." ) , run : ( ) => this . _pickAndPersistDefaultFormatter ( formatter , document ) . then ( resolve , reject ) } ] ,
91+ { silent : mode === FormattingMode . Silent , onCancel : resolve }
92+ ) ;
93+
94+ if ( mode === FormattingMode . Silent ) {
95+ // don't wait when formatting happens without interaction
96+ // but pick some formatter...
97+ resolve ( formatter [ 0 ] ) ;
98+ }
99+ } ) ;
100+ }
101+
102+ private async _pickAndPersistDefaultFormatter < T extends DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider > ( formatter : T [ ] , document : ITextModel ) : Promise < T | undefined > {
103+ const picks = formatter . map ( ( formatter , index ) => {
104+ return < IIndexedPick > {
105+ index,
106+ label : formatter . displayName || formatter . extensionId || '?'
107+ } ;
108+ } ) ;
109+ const langName = this . _modeService . getLanguageName ( document . getModeId ( ) ) || document . getModeId ( ) ;
110+ const pick = await this . _quickInputService . pick ( picks , { placeHolder : nls . localize ( 'select' , "Select a default formatter for {0}-files" , DefaultFormatter . _maybeQuotes ( langName ) ) } ) ;
111+ if ( ! pick || ! formatter [ pick . index ] . extensionId ) {
112+ return undefined ;
113+ }
114+ this . _configService . updateValue ( DefaultFormatter . configName , formatter [ pick . index ] . extensionId ! . value , {
115+ resource : document . uri ,
116+ overrideIdentifier : document . getModeId ( )
117+ } ) ;
118+ return formatter [ pick . index ] ;
119+ }
120+ }
121+
122+ Registry . as < IWorkbenchContributionsRegistry > ( WorkbenchExtensions . Workbench ) . registerWorkbenchContribution (
123+ DefaultFormatter ,
124+ LifecyclePhase . Restored
125+ ) ;
126+
127+ Registry . as < IConfigurationRegistry > ( ConfigurationExtensions . Configuration ) . registerConfiguration ( {
128+ id : 'editor' ,
129+ order : 5 ,
130+ type : 'object' ,
131+ overridable : true ,
132+ properties : {
133+ [ DefaultFormatter . configName ] : {
134+ description : nls . localize ( 'formatter.default' , "Defines a default formatter takes precedence over all other formatter settings. Must be the identifier of an extension contributing a formatter." ) ,
135+ type : 'string' ,
136+ enum : DefaultFormatter . extensionIds ,
137+ markdownEnumDescriptions : DefaultFormatter . extensionDescriptions
138+ }
139+ }
140+ } ) ;
21141
22142interface IIndexedPick extends IQuickPickItem {
23143 index : number ;
@@ -69,25 +189,27 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction {
69189 }
70190 const instaService = accessor . get ( IInstantiationService ) ;
71191 const quickPickService = accessor . get ( IQuickInputService ) ;
72- const viewletService = accessor . get ( IViewletService ) ;
73192 const telemetryService = accessor . get ( ITelemetryService ) ;
193+ const configService = accessor . get ( IConfigurationService ) ;
194+
74195 const model = editor . getModel ( ) ;
196+ const defaultFormatter = configService . getValue < string > ( DefaultFormatter . configName , {
197+ resource : model . uri ,
198+ overrideIdentifier : model . getModeId ( )
199+ } ) ;
75200
76201 const provider = getRealAndSyntheticDocumentFormattersOrdered ( model ) ;
77202 const picks = provider . map ( ( provider , index ) => {
78203 return < IIndexedPick > {
79204 index,
80205 label : provider . displayName || '' ,
206+ description : ExtensionIdentifier . equals ( provider . extensionId , defaultFormatter ) ? nls . localize ( 'def' , "(default)" ) : undefined ,
81207 buttons : [ openExtensionAction ]
82208 } ;
83209 } ) ;
84210
85211 const pick = await quickPickService . pick ( picks , {
86- placeHolder : nls . localize ( 'format.placeHolder' , "Select a formatter" ) ,
87- onDidTriggerItemButton : ( e ) => {
88- const { extensionId } = provider [ e . item . index ] ;
89- return showExtensionQuery ( viewletService , `@id:${ extensionId ! . value } ` ) ;
90- }
212+ placeHolder : nls . localize ( 'format.placeHolder' , "Select a formatter" )
91213 } ) ;
92214 if ( pick ) {
93215 await instaService . invokeFunction ( formatDocumentWithProvider , provider [ pick . index ] , editor , CancellationToken . None ) ;
@@ -119,9 +241,14 @@ registerEditorAction(class FormatSelectionMultipleAction extends EditorAction {
119241 }
120242 const instaService = accessor . get ( IInstantiationService ) ;
121243 const quickPickService = accessor . get ( IQuickInputService ) ;
122- const viewletService = accessor . get ( IViewletService ) ;
123244 const telemetryService = accessor . get ( ITelemetryService ) ;
245+ const configService = accessor . get ( IConfigurationService ) ;
246+
124247 const model = editor . getModel ( ) ;
248+ const defaultFormatter = configService . getValue < string > ( DefaultFormatter . configName , {
249+ resource : model . uri ,
250+ overrideIdentifier : model . getModeId ( )
251+ } ) ;
125252
126253 let range : Range = editor . getSelection ( ) ;
127254 if ( range . isEmpty ( ) ) {
@@ -133,16 +260,13 @@ registerEditorAction(class FormatSelectionMultipleAction extends EditorAction {
133260 return < IIndexedPick > {
134261 index,
135262 label : provider . displayName || '' ,
263+ description : ExtensionIdentifier . equals ( provider . extensionId , defaultFormatter ) ? nls . localize ( 'def' , "(default)" ) : undefined ,
136264 buttons : [ openExtensionAction ]
137265 } ;
138266 } ) ;
139267
140268 const pick = await quickPickService . pick ( picks , {
141- placeHolder : nls . localize ( 'format.placeHolder' , "Select a formatter" ) ,
142- onDidTriggerItemButton : ( e ) => {
143- const { extensionId } = provider [ e . item . index ] ;
144- return showExtensionQuery ( viewletService , `@id:${ extensionId ! . value } ` ) ;
145- }
269+ placeHolder : nls . localize ( 'format.placeHolder' , "Select a formatter" )
146270 } ) ;
147271 if ( pick ) {
148272 await instaService . invokeFunction ( formatDocumentRangeWithProvider , provider [ pick . index ] , editor , range , CancellationToken . None ) ;
0 commit comments