Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.

Commit 27cc755

Browse files
alxhubKeen Yee Liau
authored andcommitted
feat: support autocompletion details
The Angular Language Service for Ivy will be able to provide completion details for a template location, so we need to add `resolveProvider` to the capabilities, and to translate completion resolution requests between LSP and the `ts.LanguageService`.
1 parent 64a39c2 commit 27cc755

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

integration/lsp/viewengine_spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ describe('Angular language server', () => {
6262
},
6363
newText: 'charAt()',
6464
},
65+
// The 'data' field is only meaningful in Ivy mode.
66+
data: jasmine.anything(),
6567
});
6668
});
6769

server/src/completion.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,42 @@ enum CompletionKind {
2727
entity = 'entity',
2828
}
2929

30+
/**
31+
* Information about the origin of an `lsp.CompletionItem`, which is stored in the
32+
* `lsp.CompletionItem.data` property.
33+
*
34+
* On future requests for details about a completion item, this information allows the language
35+
* service to determine the context for the original completion request, in order to return more
36+
* detailed results.
37+
*/
38+
export interface NgCompletionOriginData {
39+
/**
40+
* Used to validate the type of `lsp.CompletionItem.data` is correct, since that field is type
41+
* `any`.
42+
*/
43+
kind: 'ngCompletionOriginData';
44+
45+
filePath: string;
46+
position: lsp.Position;
47+
}
48+
49+
/**
50+
* Extract `NgCompletionOriginData` from an `lsp.CompletionItem` if present.
51+
*/
52+
export function readNgCompletionData(item: lsp.CompletionItem): NgCompletionOriginData|null {
53+
if (item.data === undefined) {
54+
return null;
55+
}
56+
57+
// Validate that `item.data.kind` is actually the right tag, and narrow its type in the process.
58+
const data: NgCompletionOriginData|{kind?: never} = item.data;
59+
if (data.kind !== 'ngCompletionOriginData') {
60+
return null;
61+
}
62+
63+
return data;
64+
}
65+
3066
/**
3167
* Convert Angular's CompletionKind to LSP CompletionItemKind.
3268
* @param kind Angular's CompletionKind
@@ -81,5 +117,10 @@ export function tsCompletionEntryToLspCompletionItem(
81117
item.textEdit = entry.replacementSpan ?
82118
lsp.TextEdit.replace(tsTextSpanToLspRange(scriptInfo, entry.replacementSpan), insertText) :
83119
lsp.TextEdit.insert(position, insertText);
120+
item.data = {
121+
kind: 'ngCompletionOriginData',
122+
filePath: scriptInfo.fileName,
123+
position,
124+
} as NgCompletionOriginData;
84125
return item;
85126
}

server/src/session.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {ServerOptions} from '../common/initialize';
1313
import {ProjectLanguageService, ProjectLoadingFinish, ProjectLoadingStart} from '../common/notifications';
1414
import {NgccProgressToken, NgccProgressType} from '../common/progress';
1515

16-
import {tsCompletionEntryToLspCompletionItem} from './completion';
16+
import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
1717
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
1818
import {resolveAndRunNgcc} from './ngcc';
1919
import {ServerHost} from './server_host';
@@ -113,6 +113,7 @@ export class Session {
113113
conn.onReferences(p => this.onReferences(p));
114114
conn.onHover(p => this.onHover(p));
115115
conn.onCompletion(p => this.onCompletion(p));
116+
conn.onCompletionResolve(p => this.onCompletionResolve(p));
116117
}
117118

118119
private async runNgcc(configFilePath: string) {
@@ -313,9 +314,8 @@ export class Session {
313314
capabilities: {
314315
textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
315316
completionProvider: {
316-
// The server does not provide support to resolve additional information
317-
// for a completion item.
318-
resolveProvider: false,
317+
// Only the Ivy LS provides support for additional completion resolution.
318+
resolveProvider: this.ivy,
319319
triggerCharacters: ['<', '.', '*', '[', '(', '$', '|']
320320
},
321321
definitionProvider: true,
@@ -581,6 +581,42 @@ export class Session {
581581
(e) => tsCompletionEntryToLspCompletionItem(e, params.position, scriptInfo));
582582
}
583583

584+
private onCompletionResolve(item: lsp.CompletionItem): lsp.CompletionItem {
585+
const data = readNgCompletionData(item);
586+
if (data === null) {
587+
// This item wasn't tagged with Angular LS completion data - it probably didn't originate from
588+
// this language service.
589+
return item;
590+
}
591+
592+
const {filePath, position} = data;
593+
const lsInfo = this.getLSAndScriptInfo(filePath);
594+
if (lsInfo === undefined) {
595+
return item;
596+
}
597+
const {languageService, scriptInfo} = lsInfo;
598+
599+
const offset = lspPositionToTsPosition(scriptInfo, position);
600+
const details = languageService.getCompletionEntryDetails(
601+
filePath, offset, item.insertText ?? item.label, undefined, undefined, undefined);
602+
if (details === undefined) {
603+
return item;
604+
}
605+
606+
const {kind, kindModifiers, displayParts, documentation} = details;
607+
let desc = kindModifiers ? kindModifiers + ' ' : '';
608+
if (displayParts) {
609+
// displayParts does not contain info about kindModifiers
610+
// but displayParts does contain info about kind
611+
desc += displayParts.map(dp => dp.text).join('');
612+
} else {
613+
desc += kind;
614+
}
615+
item.detail = desc;
616+
item.documentation = documentation?.map(d => d.text).join('');
617+
return item;
618+
}
619+
584620
/**
585621
* Show an error message in the remote console and log to file.
586622
*

0 commit comments

Comments
 (0)