Skip to content

Commit 1a8c837

Browse files
committed
feat: provide snippets for attribute
1 parent 7a3ac82 commit 1a8c837

File tree

8 files changed

+87
-3
lines changed

8 files changed

+87
-3
lines changed

client/src/client.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,12 @@ function constructArgs(ctx: vscode.ExtensionContext): string[] {
454454
args.push('--includeAutomaticOptionalChainCompletions');
455455
}
456456

457+
const includeCompletionsWithSnippetText =
458+
config.get<boolean>('angular.suggest.includeCompletionsWithSnippetText');
459+
if (includeCompletionsWithSnippetText) {
460+
args.push('--includeCompletionsWithSnippetText');
461+
}
462+
457463
const tsdk: string|null = config.get('typescript.tsdk', null);
458464
const tsProbeLocations = getProbeLocations(tsdk, ctx.extensionPath);
459465
args.push('--tsProbeLocations', tsProbeLocations.join(','));

integration/lsp/ivy_spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,62 @@ describe('auto-apply optional chaining', () => {
572572
});
573573
});
574574

575+
describe('insert snippet text', () => {
576+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; /* 10 seconds */
577+
578+
let client: MessageConnection;
579+
beforeEach(async () => {
580+
client = createConnection({
581+
ivy: true,
582+
includeCompletionsWithSnippetText: true,
583+
});
584+
// If debugging, set to
585+
// - lsp.Trace.Messages to inspect request/response/notification, or
586+
// - lsp.Trace.Verbose to inspect payload
587+
client.trace(lsp.Trace.Off, createTracer());
588+
client.listen();
589+
await initializeServer(client);
590+
});
591+
592+
afterEach(() => {
593+
client.dispose();
594+
});
595+
596+
it('should be able to complete for an attribute with the value is empty', async () => {
597+
openTextDocument(client, FOO_TEMPLATE, `<my-app appOut></my-app>`);
598+
const languageServiceEnabled = await waitForNgcc(client);
599+
expect(languageServiceEnabled).toBeTrue();
600+
const response = await client.sendRequest(lsp.CompletionRequest.type, {
601+
textDocument: {
602+
uri: FOO_TEMPLATE_URI,
603+
},
604+
position: {line: 0, character: 14},
605+
}) as lsp.CompletionItem[];
606+
const completion = response.find(i => i.label === '(appOutput)')!;
607+
expect(completion.kind).toEqual(lsp.CompletionItemKind.Event);
608+
expect(completion.insertText).toEqual('(appOutput)="$1"');
609+
expect(completion.insertTextFormat).toEqual(lsp.InsertTextFormat.Snippet);
610+
expect((completion.textEdit as lsp.TextEdit).newText).toEqual('appOut');
611+
});
612+
613+
it('should not be included in the completion for an attribute with a value', async () => {
614+
openTextDocument(client, FOO_TEMPLATE, `<my-app [appInput]="1"></my-app>`);
615+
const languageServiceEnabled = await waitForNgcc(client);
616+
expect(languageServiceEnabled).toBeTrue();
617+
const response = await client.sendRequest(lsp.CompletionRequest.type, {
618+
textDocument: {
619+
uri: FOO_TEMPLATE_URI,
620+
},
621+
position: {line: 0, character: 17},
622+
}) as lsp.CompletionItem[];
623+
const completion = response.find(i => i.label === '[appInput]')!;
624+
expect(completion.kind).toEqual(lsp.CompletionItemKind.Property);
625+
expect(completion.insertText).toBeUndefined;
626+
expect(completion.insertTextFormat).toBeUndefined;
627+
expect((completion.textEdit as lsp.TextEdit).newText).toEqual('appInput');
628+
});
629+
});
630+
575631
function onNgccProgress(client: MessageConnection): Promise<string> {
576632
return new Promise(resolve => {
577633
client.onProgress(NgccProgressType, NgccProgressToken, (params: NgccProgress) => {

integration/lsp/test_utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {PROJECT_PATH, SERVER_PATH} from '../test_constants';
1717
export interface ServerOptions {
1818
ivy: boolean;
1919
includeAutomaticOptionalChainCompletions?: boolean;
20+
includeCompletionsWithSnippetText?: boolean;
2021
}
2122

2223
export function createConnection(serverOptions: ServerOptions): MessageConnection {

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@
118118
"type": "boolean",
119119
"default": true,
120120
"description": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires TS 3.7+ and strict null checks to be enabled."
121+
},
122+
"angular.suggest.includeCompletionsWithSnippetText": {
123+
"type": "boolean",
124+
"default": true,
125+
"description": "Enable/disable snippet completions from angular language Server. Requires using TypeScript 4.3+ in the workspace."
121126
}
122127
}
123128
},

server/src/cmdline_utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface CommandLineOptions {
3838
ngProbeLocations: string[];
3939
tsProbeLocations: string[];
4040
includeAutomaticOptionalChainCompletions: boolean;
41+
includeCompletionsWithSnippetText: boolean;
4142
}
4243

4344
export function parseCommandLine(argv: string[]): CommandLineOptions {
@@ -51,6 +52,7 @@ export function parseCommandLine(argv: string[]): CommandLineOptions {
5152
tsProbeLocations: parseStringArray(argv, '--tsProbeLocations'),
5253
includeAutomaticOptionalChainCompletions:
5354
hasArgument(argv, '--includeAutomaticOptionalChainCompletions'),
55+
includeCompletionsWithSnippetText: hasArgument(argv, '--includeCompletionsWithSnippetText'),
5456
};
5557
}
5658

server/src/completion.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ export function tsCompletionEntryToLspCompletionItem(
125125
// range will include the dot. the `insertText` should be assigned to the `filterText` to filter
126126
// the completion items.
127127
item.filterText = entry.insertText;
128+
if (entry.isSnippet) {
129+
item.insertTextFormat = lsp.InsertTextFormat.Snippet;
130+
}
128131
}
129132

130133
item.data = {

server/src/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ function main() {
4444
resolvedNgLsPath: ng.resolvedPath,
4545
ivy: isG3 ? true : options.ivy,
4646
logToConsole: options.logToConsole,
47-
includeAutomaticOptionalChainCompletions: options.includeAutomaticOptionalChainCompletions
47+
includeAutomaticOptionalChainCompletions: options.includeAutomaticOptionalChainCompletions,
48+
includeCompletionsWithSnippetText: options.includeCompletionsWithSnippetText,
4849
});
4950

5051
// Log initialization info

server/src/session.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface SessionOptions {
3030
ivy: boolean;
3131
logToConsole: boolean;
3232
includeAutomaticOptionalChainCompletions: boolean;
33+
includeCompletionsWithSnippetText: boolean;
3334
}
3435

3536
enum LanguageId {
@@ -54,6 +55,8 @@ export class Session {
5455
private readonly logToConsole: boolean;
5556
private readonly openFiles = new MruTracker();
5657
private readonly includeAutomaticOptionalChainCompletions: boolean;
58+
private readonly includeCompletionsWithSnippetText: boolean;
59+
private snippetSupport: boolean|undefined;
5760
// Tracks the spawn order and status of the `ngcc` processes. This allows us to ensure we enable
5861
// the LS in the same order the projects were created in.
5962
private projectNgccQueue: Array<{project: ts.server.Project, done: boolean}> = [];
@@ -71,6 +74,7 @@ export class Session {
7174
constructor(options: SessionOptions) {
7275
this.includeAutomaticOptionalChainCompletions =
7376
options.includeAutomaticOptionalChainCompletions;
77+
this.includeCompletionsWithSnippetText = options.includeCompletionsWithSnippetText;
7478
this.logger = options.logger;
7579
this.ivy = options.ivy;
7680
this.logToConsole = options.logToConsole;
@@ -605,6 +609,8 @@ export class Session {
605609
}
606610

607611
private onInitialize(params: lsp.InitializeParams): lsp.InitializeResult {
612+
this.snippetSupport =
613+
params.capabilities.textDocument?.completion?.completionItem?.snippetSupport;
608614
const serverOptions: ServerOptions = {
609615
logFile: this.logger.getLogFileName(),
610616
};
@@ -1007,10 +1013,14 @@ export class Session {
10071013
const offset = lspPositionToTsPosition(scriptInfo, params.position);
10081014

10091015
let options: ts.GetCompletionsAtPositionOptions = {};
1010-
if (this.includeAutomaticOptionalChainCompletions) {
1016+
const includeCompletionsWithSnippetText =
1017+
this.includeCompletionsWithSnippetText && this.snippetSupport;
1018+
if (this.includeAutomaticOptionalChainCompletions || includeCompletionsWithSnippetText) {
10111019
options = {
10121020
includeAutomaticOptionalChainCompletions: this.includeAutomaticOptionalChainCompletions,
1013-
includeCompletionsWithInsertText: this.includeAutomaticOptionalChainCompletions,
1021+
includeCompletionsWithSnippetText: includeCompletionsWithSnippetText,
1022+
includeCompletionsWithInsertText:
1023+
this.includeAutomaticOptionalChainCompletions || includeCompletionsWithSnippetText,
10141024
};
10151025
}
10161026

0 commit comments

Comments
 (0)