@@ -19,7 +19,7 @@ import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './comp
1919import { tsDiagnosticToLspDiagnostic } from './diagnostic' ;
2020import { resolveAndRunNgcc } from './ngcc' ;
2121import { 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' ;
2323
2424export interface SessionOptions {
2525 host : ServerHost ;
@@ -48,6 +48,9 @@ enum NgccErrorMessageAction {
4848 showOutput ,
4949}
5050
51+ const defaultFormatOptions : ts . FormatCodeSettings = { } ;
52+ const defaultPreferences : ts . UserPreferences = { } ;
53+
5154/**
5255 * Session is a wrapper around lsp.IConnection, with all the necessary protocol
5356 * handlers installed for Angular language service.
@@ -197,6 +200,67 @@ export class Session {
197200 conn . onCodeLens ( p => this . onCodeLens ( p ) ) ;
198201 conn . onCodeLensResolve ( p => this . onCodeLensResolve ( p ) ) ;
199202 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+ } ;
200264 }
201265
202266 private isInAngularProject ( params : IsInAngularProjectParams ) : boolean | null {
@@ -663,6 +727,10 @@ export class Session {
663727 workspace : {
664728 workspaceFolders : { supported : true } ,
665729 } ,
730+ codeActionProvider : this . ivy ? {
731+ resolveProvider : true ,
732+ } :
733+ undefined ,
666734 } ,
667735 serverOptions,
668736 } ;
@@ -1239,3 +1307,43 @@ function isTypeScriptFile(path: string): boolean {
12391307function isExternalTemplate ( path : string ) : boolean {
12401308 return ! isTypeScriptFile ( path ) ;
12411309}
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