@@ -22,9 +22,14 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
22
22
import { IStoredFileWorkingCopy , IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy' ;
23
23
import { IStoredFileWorkingCopySaveParticipant , IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService' ;
24
24
import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon' ;
25
- import { ICommandService } from 'vs/platform/commands/ common/commands ' ;
25
+ import { ITextModel } from 'vs/editor/ common/model ' ;
26
26
import { ILogService } from 'vs/platform/log/common/log' ;
27
27
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust' ;
28
+ import { CodeActionKind , CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types' ;
29
+ import { CodeActionTriggerType , CodeActionProvider , IWorkspaceTextEdit } from 'vs/editor/common/languages' ;
30
+ import { applyCodeAction , ApplyCodeActionReason , getCodeActions } from 'vs/editor/contrib/codeAction/browser/codeAction' ;
31
+ import { isEqual } from 'vs/base/common/resources' ;
32
+
28
33
29
34
class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant {
30
35
constructor (
@@ -91,13 +96,15 @@ class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant {
91
96
class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant {
92
97
constructor (
93
98
@IConfigurationService private readonly configurationService : IConfigurationService ,
94
- @ICommandService private readonly commandService : ICommandService ,
95
99
@ILogService private readonly logService : ILogService ,
96
100
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService : IWorkspaceTrustManagementService ,
101
+ @ILanguageFeaturesService private readonly languageFeaturesService : ILanguageFeaturesService ,
102
+ @ITextModelService private readonly textModelService : ITextModelService ,
103
+ @IInstantiationService private readonly instantiationService : IInstantiationService ,
97
104
) {
98
105
}
99
106
100
- async participate ( workingCopy : IStoredFileWorkingCopy < IStoredFileWorkingCopyModel > , context : { reason : SaveReason } , progress : IProgress < IProgressStep > , _token : CancellationToken ) : Promise < void > {
107
+ async participate ( workingCopy : IStoredFileWorkingCopy < IStoredFileWorkingCopyModel > , context : { reason : SaveReason } , progress : IProgress < IProgressStep > , token : CancellationToken ) : Promise < void > {
101
108
const isTrusted = this . workspaceTrustManagementService . isWorkspaceTrusted ( ) ;
102
109
if ( ! isTrusted ) {
103
110
return ;
@@ -111,11 +118,13 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa
111
118
return undefined ;
112
119
}
113
120
114
- const setting = this . configurationService . getValue < { [ kind : string ] : boolean } | string [ ] > ( 'notebook.experimental. codeActionsOnSave' ) ;
121
+ const setting = this . configurationService . getValue < { [ kind : string ] : boolean } | string [ ] > ( NotebookSetting . codeActionsOnSave ) ;
115
122
if ( ! setting ) {
116
123
return undefined ;
117
124
}
118
125
126
+ const notebookModel = workingCopy . model . notebookModel ;
127
+
119
128
const settingItems : string [ ] = Array . isArray ( setting )
120
129
? setting
121
130
: Object . keys ( setting ) . filter ( x => setting [ x ] ) ;
@@ -124,21 +133,136 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa
124
133
return undefined ;
125
134
}
126
135
127
- progress . report ( { message : 'CodeActionsOnSave running' } ) ;
136
+ const codeActionsOnSave = this . createCodeActionsOnSave ( settingItems ) ;
137
+
138
+ // TODO: potentially modify to account for new `Notebook` code action kind
139
+ // prioritize `source.fixAll` code actions
140
+ if ( ! Array . isArray ( setting ) ) {
141
+ codeActionsOnSave . sort ( ( a , b ) => {
142
+ if ( CodeActionKind . SourceFixAll . contains ( a ) ) {
143
+ if ( CodeActionKind . SourceFixAll . contains ( b ) ) {
144
+ return 0 ;
145
+ }
146
+ return - 1 ;
147
+ }
148
+ if ( CodeActionKind . SourceFixAll . contains ( b ) ) {
149
+ return 1 ;
150
+ }
151
+ return 0 ;
152
+ } ) ;
153
+ }
154
+
155
+ if ( ! codeActionsOnSave . length ) {
156
+ return undefined ;
157
+ }
158
+
159
+ const excludedActions = Array . isArray ( setting )
160
+ ? [ ]
161
+ : Object . keys ( setting )
162
+ . filter ( x => setting [ x ] === false )
163
+ . map ( x => new CodeActionKind ( x ) ) ;
164
+
165
+
166
+ progress . report ( { message : localize ( 'notebookSaveParticipants.codeActions' , "Running code actions" ) } ) ;
128
167
const disposable = new DisposableStore ( ) ;
129
168
try {
130
- for ( const cmd of settingItems ) {
131
- await this . commandService . executeCommand ( cmd ) ;
132
- }
169
+ await Promise . all ( notebookModel . cells . map ( async cell => {
170
+ const ref = await this . textModelService . createModelReference ( cell . uri ) ;
171
+ disposable . add ( ref ) ;
172
+
173
+ const textEditorModel = ref . object . textEditorModel ;
174
+
175
+ await this . applyOnSaveActions ( textEditorModel , codeActionsOnSave , excludedActions , progress , token ) ;
176
+ } ) ) ;
133
177
} catch {
134
- // Failure to apply a code action should not block other on save actions
135
- this . logService . warn ( 'CodeActionsOnSave failed to apply a code action' ) ;
178
+ this . logService . error ( 'Failed to apply code action on save' ) ;
136
179
} finally {
137
180
progress . report ( { increment : 100 } ) ;
138
181
disposable . dispose ( ) ;
139
182
}
140
183
}
184
+
185
+ private createCodeActionsOnSave ( settingItems : readonly string [ ] ) : CodeActionKind [ ] {
186
+ const kinds = settingItems . map ( x => new CodeActionKind ( x ) ) ;
187
+
188
+ // Remove subsets
189
+ return kinds . filter ( kind => {
190
+ return kinds . every ( otherKind => otherKind . equals ( kind ) || ! otherKind . contains ( kind ) ) ;
191
+ } ) ;
192
+ }
193
+
194
+ private async applyOnSaveActions ( model : ITextModel , codeActionsOnSave : readonly CodeActionKind [ ] , excludes : readonly CodeActionKind [ ] , progress : IProgress < IProgressStep > , token : CancellationToken ) : Promise < void > {
195
+
196
+ const getActionProgress = new class implements IProgress < CodeActionProvider > {
197
+ private _names = new Set < string > ( ) ;
198
+ private _report ( ) : void {
199
+ progress . report ( {
200
+ message : localize (
201
+ { key : 'codeaction.get2' , comment : [ '[configure]({1}) is a link. Only translate `configure`. Do not change brackets and parentheses or {1}' ] } ,
202
+ "Getting code actions from '{0}' ([configure]({1}))." ,
203
+ [ ...this . _names ] . map ( name => `'${ name } '` ) . join ( ', ' ) ,
204
+ 'command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D'
205
+ )
206
+ } ) ;
207
+ }
208
+ report ( provider : CodeActionProvider ) {
209
+ if ( provider . displayName && ! this . _names . has ( provider . displayName ) ) {
210
+ this . _names . add ( provider . displayName ) ;
211
+ this . _report ( ) ;
212
+ }
213
+ }
214
+ } ;
215
+
216
+ for ( const codeActionKind of codeActionsOnSave ) {
217
+ const actionsToRun = await this . getActionsToRun ( model , codeActionKind , excludes , getActionProgress , token ) ;
218
+ if ( token . isCancellationRequested ) {
219
+ actionsToRun . dispose ( ) ;
220
+ return ;
221
+ }
222
+
223
+ try {
224
+ for ( const action of actionsToRun . validActions ) {
225
+ const codeActionEdits = action . action . edit ?. edits ;
226
+ let breakFlag = false ;
227
+ for ( const edit of codeActionEdits ?? [ ] ) {
228
+ const workspaceTextEdit = edit as IWorkspaceTextEdit ;
229
+ if ( workspaceTextEdit . resource && isEqual ( workspaceTextEdit . resource , model . uri ) ) {
230
+ continue ;
231
+ } else {
232
+ // error -> applied to multiple resources
233
+ breakFlag = true ;
234
+ break ;
235
+ }
236
+ }
237
+ if ( breakFlag ) {
238
+ this . logService . warn ( 'Failed to apply code action on save, applied to multiple resources.' ) ;
239
+ continue ;
240
+ }
241
+ progress . report ( { message : localize ( 'codeAction.apply' , "Applying code action '{0}'." , action . action . title ) } ) ;
242
+ await this . instantiationService . invokeFunction ( applyCodeAction , action , ApplyCodeActionReason . OnSave , { } , token ) ;
243
+ if ( token . isCancellationRequested ) {
244
+ return ;
245
+ }
246
+ }
247
+ } catch {
248
+ // Failure to apply a code action should not block other on save actions
249
+ } finally {
250
+ actionsToRun . dispose ( ) ;
251
+ }
252
+ }
253
+ }
254
+
255
+ private getActionsToRun ( model : ITextModel , codeActionKind : CodeActionKind , excludes : readonly CodeActionKind [ ] , progress : IProgress < CodeActionProvider > , token : CancellationToken ) {
256
+ return getCodeActions ( this . languageFeaturesService . codeActionProvider , model , model . getFullModelRange ( ) , {
257
+ type : CodeActionTriggerType . Auto ,
258
+ triggerAction : CodeActionTriggerSource . OnSave ,
259
+ filter : { include : codeActionKind , excludes : excludes , includeSourceActions : true } ,
260
+ } , progress , token ) ;
261
+ }
141
262
}
263
+
264
+
265
+
142
266
export class SaveParticipantsContribution extends Disposable implements IWorkbenchContribution {
143
267
constructor (
144
268
@IInstantiationService private readonly instantiationService : IInstantiationService ,
0 commit comments