@@ -19,7 +19,7 @@ import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './comp
19
19
import { tsDiagnosticToLspDiagnostic } from './diagnostic' ;
20
20
import { resolveAndRunNgcc } from './ngcc' ;
21
21
import { ServerHost } from './server_host' ;
22
- import { filePathToUri , getMappedDefinitionInfo , isConfiguredProject , isDebugMode , lspPositionToTsPosition , lspRangeToTsPositions , MruTracker , tsDisplayPartsToText , tsTextSpanToLspRange , uriToFilePath } from './utils' ;
22
+ import { filePathToUri , getMappedDefinitionInfo , isConfiguredProject , isDebugMode , lspPositionToTsPosition , lspRangeToTsPositions , MruTracker , tsDisplayPartsToText , tsFileTextChangesToLspWorkspaceEdit , tsTextSpanToLspRange , uriToFilePath } from './utils' ;
23
23
24
24
export interface SessionOptions {
25
25
host : ServerHost ;
@@ -48,6 +48,9 @@ enum NgccErrorMessageAction {
48
48
showOutput ,
49
49
}
50
50
51
+ const defaultFormatOptions : ts . FormatCodeSettings = { } ;
52
+ const defaultPreferences : ts . UserPreferences = { } ;
53
+
51
54
/**
52
55
* Session is a wrapper around lsp.IConnection, with all the necessary protocol
53
56
* handlers installed for Angular language service.
@@ -197,6 +200,67 @@ export class Session {
197
200
conn . onCodeLens ( p => this . onCodeLens ( p ) ) ;
198
201
conn . onCodeLensResolve ( p => this . onCodeLensResolve ( p ) ) ;
199
202
conn . onSignatureHelp ( p => this . onSignatureHelp ( p ) ) ;
203
+ conn . onCodeAction ( p => this . onCodeAction ( p ) ) ;
204
+ conn . onCodeActionResolve ( p => this . onCodeActionResolve ( p ) ) ;
205
+ }
206
+
207
+ private onCodeAction ( params : lsp . CodeActionParams ) : lsp . CodeAction [ ] | null {
208
+ const filePath = uriToFilePath ( params . textDocument . uri ) ;
209
+ const lsInfo = this . getLSAndScriptInfo ( params . textDocument ) ;
210
+ if ( ! lsInfo ) {
211
+ return null ;
212
+ }
213
+ const start = lspPositionToTsPosition ( lsInfo . scriptInfo , params . range . start ) ;
214
+ const end = lspPositionToTsPosition ( lsInfo . scriptInfo , params . range . end ) ;
215
+ const errorCodes = params . context . diagnostics . map ( diag => diag . code )
216
+ . filter ( ( code ) : code is number => typeof code === 'number' ) ;
217
+
218
+ const codeActions = lsInfo . languageService . getCodeFixesAtPosition (
219
+ filePath , start , end , errorCodes , defaultFormatOptions , defaultPreferences ) ;
220
+ const individualCodeFixes = codeActions . map < lsp . CodeAction > ( codeAction => {
221
+ return {
222
+ title : codeAction . description ,
223
+ kind : lsp . CodeActionKind . QuickFix ,
224
+ diagnostics : params . context . diagnostics ,
225
+ edit : tsFileTextChangesToLspWorkspaceEdit (
226
+ codeAction . changes , ( path : string ) => this . projectService . getScriptInfo ( path ) ) ,
227
+ } ;
228
+ } ) ;
229
+ const codeFixesAll = getCodeFixesAll ( codeActions , params . textDocument ) ;
230
+ return [ ...individualCodeFixes , ...codeFixesAll ] ;
231
+ }
232
+
233
+ private onCodeActionResolve ( param : lsp . CodeAction ) : lsp . CodeAction {
234
+ const codeActionResolve = param . data as unknown as CodeActionResolveData ;
235
+ /**
236
+ * Now `@angular/language-service` only support quick fix, so the `onCodeAction` will return the
237
+ * `edit` of the `lsp.CodeAction` for the diagnostics in the range that the user selects except
238
+ * the fix all code actions.
239
+ *
240
+ * And the function `getCombinedCodeFix` only cares about the `fixId` and the `document`.
241
+ * https://github.com/microsoft/vscode/blob/8ba9963c2edb08d54f2b7221137d6f1de79ecc09/extensions/typescript-language-features/src/languageFeatures/quickFix.ts#L258
242
+ */
243
+ const isCodeFixesAll = codeActionResolve . fixId !== undefined ;
244
+ if ( ! isCodeFixesAll ) {
245
+ return param ;
246
+ }
247
+ const filePath = uriToFilePath ( codeActionResolve . document . uri ) ;
248
+ const lsInfo = this . getLSAndScriptInfo ( codeActionResolve . document ) ;
249
+ if ( ! lsInfo ) {
250
+ return param ;
251
+ }
252
+ const fixesAllChanges = lsInfo . languageService . getCombinedCodeFix (
253
+ {
254
+ type : 'file' ,
255
+ fileName : filePath ,
256
+ } ,
257
+ codeActionResolve . fixId as { } , defaultFormatOptions , defaultPreferences ) ;
258
+
259
+ return {
260
+ title : param . title ,
261
+ edit : tsFileTextChangesToLspWorkspaceEdit (
262
+ fixesAllChanges . changes , ( path ) => this . projectService . getScriptInfo ( path ) ) ,
263
+ } ;
200
264
}
201
265
202
266
private isInAngularProject ( params : IsInAngularProjectParams ) : boolean | null {
@@ -663,6 +727,10 @@ export class Session {
663
727
workspace : {
664
728
workspaceFolders : { supported : true } ,
665
729
} ,
730
+ codeActionProvider : this . ivy ? {
731
+ resolveProvider : true ,
732
+ } :
733
+ undefined ,
666
734
} ,
667
735
serverOptions,
668
736
} ;
@@ -1239,3 +1307,43 @@ function isTypeScriptFile(path: string): boolean {
1239
1307
function isExternalTemplate ( path : string ) : boolean {
1240
1308
return ! isTypeScriptFile ( path ) ;
1241
1309
}
1310
+
1311
+ interface CodeActionResolveData {
1312
+ fixId ?: string ;
1313
+ document : lsp . TextDocumentIdentifier ;
1314
+ }
1315
+
1316
+ /**
1317
+ * Extract the fixAll action from `codeActions`
1318
+ *
1319
+ * When getting code fixes at the specified cursor position, the LS will return the code actions
1320
+ * that tell the editor how to fix it. For each code action, if the document includes multi
1321
+ * same-type errors, the `fixId` will append to it, because they are not `complete`. This function
1322
+ * will extract them, and they will be resolved lazily in the `onCodeActionResolve` function.
1323
+ *
1324
+ * Now the client can only resolve the `edit` property.
1325
+ * https://github.com/microsoft/vscode-languageserver-node/blob/f97bb73dbfb920af4bc8c13ecdcdc16359cdeda6/client/src/common/codeAction.ts#L45
1326
+ */
1327
+ function getCodeFixesAll (
1328
+ codeActions : readonly ts . CodeFixAction [ ] ,
1329
+ document : lsp . TextDocumentIdentifier ) : lsp . CodeAction [ ] {
1330
+ const seenFixId = new Set < string > ( ) ;
1331
+ const lspCodeActions : lsp . CodeAction [ ] = [ ] ;
1332
+ for ( const codeAction of codeActions ) {
1333
+ const fixId = codeAction . fixId as string | undefined ;
1334
+ if ( fixId === undefined || codeAction . fixAllDescription === undefined || seenFixId . has ( fixId ) ) {
1335
+ continue ;
1336
+ }
1337
+ seenFixId . add ( fixId ) ;
1338
+ const codeActionResolveData : CodeActionResolveData = {
1339
+ fixId,
1340
+ document,
1341
+ } ;
1342
+ lspCodeActions . push ( {
1343
+ title : codeAction . fixAllDescription ,
1344
+ kind : lsp . CodeActionKind . QuickFix ,
1345
+ data : codeActionResolveData ,
1346
+ } ) ;
1347
+ }
1348
+ return lspCodeActions ;
1349
+ }
0 commit comments