Skip to content

Commit 22f2b11

Browse files
author
Andy
authored
Add 'triggerCharacter' option for completions requests (microsoft#23491)
* Add 'triggerCharacter' option for completions requests * mhegazy code review * More code review * Handle '<' in comment
1 parent 2826bc7 commit 22f2b11

File tree

11 files changed

+85
-6
lines changed

11 files changed

+85
-6
lines changed

src/harness/fourslash.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ namespace FourSlash {
842842

843843
const actualCompletions = this.getCompletionListAtCaret(options);
844844
if (!actualCompletions) {
845+
if (expected === undefined) return;
845846
this.raiseError(`No completions at position '${this.currentCaretPosition}'.`);
846847
}
847848

@@ -4653,10 +4654,12 @@ namespace FourSlashInterface {
46534654

46544655
export type ExpectedCompletionEntry = string | { name: string, insertText?: string, replacementSpan?: FourSlash.Range };
46554656
export interface CompletionsAtOptions extends Partial<ts.UserPreferences> {
4657+
triggerCharacter?: string;
46564658
isNewIdentifierLocation?: boolean;
46574659
}
46584660

46594661
export interface VerifyCompletionListContainsOptions extends ts.UserPreferences {
4662+
triggerCharacter?: string;
46604663
sourceDisplay: string;
46614664
isRecommended?: true;
46624665
insertText?: string;

src/server/protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,6 +1749,7 @@ namespace ts.server.protocol {
17491749
* Optional prefix to apply to possible completions.
17501750
*/
17511751
prefix?: string;
1752+
triggerCharacter?: string;
17521753
/**
17531754
* @deprecated Use UserPreferences.includeCompletionsForModuleExports
17541755
*/

src/server/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,7 @@ namespace ts.server {
12871287

12881288
const completions = project.getLanguageService().getCompletionsAtPosition(file, position, {
12891289
...this.getPreferences(file),
1290+
triggerCharacter: args.triggerCharacter,
12901291
includeExternalModuleExports: args.includeExternalModuleExports,
12911292
includeInsertTextCompletions: args.includeInsertTextCompletions
12921293
});

src/services/completions.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace ts.Completions {
2525

2626
const enum GlobalsSearch { Continue, Success, Fail }
2727

28-
export function getCompletionsAtPosition(host: LanguageServiceHost, program: Program, log: Log, sourceFile: SourceFile, position: number, preferences: UserPreferences): CompletionInfo | undefined {
28+
export function getCompletionsAtPosition(host: LanguageServiceHost, program: Program, log: Log, sourceFile: SourceFile, position: number, preferences: UserPreferences, triggerCharacter: string | undefined): CompletionInfo | undefined {
2929
const typeChecker = program.getTypeChecker();
3030
const compilerOptions = program.getCompilerOptions();
3131
if (isInReferenceComment(sourceFile, position)) {
@@ -34,6 +34,7 @@ namespace ts.Completions {
3434
}
3535

3636
const contextToken = findPrecedingToken(position, sourceFile);
37+
if (triggerCharacter && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) return undefined;
3738

3839
if (isInString(sourceFile, position, contextToken)) {
3940
return !contextToken || !isStringLiteralLike(contextToken)
@@ -2197,4 +2198,31 @@ namespace ts.Completions {
21972198
function hasIndexSignature(type: Type): boolean {
21982199
return !!type.getStringIndexType() || !!type.getNumberIndexType();
21992200
}
2201+
2202+
function isValidTrigger(sourceFile: SourceFile, triggerCharacter: string, contextToken: Node, position: number): boolean {
2203+
switch (triggerCharacter) {
2204+
case '"':
2205+
case "'":
2206+
case "`":
2207+
// Only automatically bring up completions if this is an opening quote.
2208+
return isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1;
2209+
case "<":
2210+
// Opening JSX tag
2211+
return contextToken.kind === SyntaxKind.LessThanToken && contextToken.parent.kind !== SyntaxKind.BinaryExpression;
2212+
default:
2213+
return Debug.fail(triggerCharacter);
2214+
}
2215+
}
2216+
2217+
function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression {
2218+
switch (node.kind) {
2219+
case SyntaxKind.StringLiteral:
2220+
case SyntaxKind.NoSubstitutionTemplateLiteral:
2221+
case SyntaxKind.TemplateExpression:
2222+
case SyntaxKind.TaggedTemplateExpression:
2223+
return true;
2224+
default:
2225+
return false;
2226+
}
2227+
}
22002228
}

src/services/services.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1409,7 +1409,8 @@ namespace ts {
14091409
log,
14101410
getValidSourceFile(fileName),
14111411
position,
1412-
fullPreferences);
1412+
fullPreferences,
1413+
options.triggerCharacter);
14131414
}
14141415

14151416
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = defaultPreferences): CompletionEntryDetails {

src/services/shims.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ namespace ts {
912912
* to provide at the given source position and providing a member completion
913913
* list if requested.
914914
*/
915-
public getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined) {
915+
public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined) {
916916
return this.forwardJSONCall(
917917
`getCompletionsAtPosition('${fileName}', ${position}, ${preferences})`,
918918
() => this.languageService.getCompletionsAtPosition(fileName, position, preferences)

src/services/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,9 @@ namespace ts {
354354

355355
export type OrganizeImportsScope = CombinedCodeFixScope;
356356

357-
/** @deprecated Use UserPreferences */
358357
export interface GetCompletionsAtPositionOptions extends UserPreferences {
358+
/** If the editor is asking for completions because a certain character was typed, and not because the user explicitly requested them, this should be set. */
359+
triggerCharacter?: string;
359360
/** @deprecated Use includeCompletionsForModuleExports */
360361
includeExternalModuleExports?: boolean;
361362
/** @deprecated Use includeCompletionsWithInsertText */

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4455,8 +4455,9 @@ declare namespace ts {
44554455
fileName: string;
44564456
}
44574457
type OrganizeImportsScope = CombinedCodeFixScope;
4458-
/** @deprecated Use UserPreferences */
44594458
interface GetCompletionsAtPositionOptions extends UserPreferences {
4459+
/** If the editor is asking for completions because a certain character was typed, and not because the user explicitly requested them, this should be set. */
4460+
triggerCharacter?: string;
44604461
/** @deprecated Use includeCompletionsForModuleExports */
44614462
includeExternalModuleExports?: boolean;
44624463
/** @deprecated Use includeCompletionsWithInsertText */
@@ -6667,6 +6668,7 @@ declare namespace ts.server.protocol {
66676668
* Optional prefix to apply to possible completions.
66686669
*/
66696670
prefix?: string;
6671+
triggerCharacter?: string;
66706672
/**
66716673
* @deprecated Use UserPreferences.includeCompletionsForModuleExports
66726674
*/

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4455,8 +4455,9 @@ declare namespace ts {
44554455
fileName: string;
44564456
}
44574457
type OrganizeImportsScope = CombinedCodeFixScope;
4458-
/** @deprecated Use UserPreferences */
44594458
interface GetCompletionsAtPositionOptions extends UserPreferences {
4459+
/** If the editor is asking for completions because a certain character was typed, and not because the user explicitly requested them, this should be set. */
4460+
triggerCharacter?: string;
44604461
/** @deprecated Use includeCompletionsForModuleExports */
44614462
includeExternalModuleExports?: boolean;
44624463
/** @deprecated Use includeCompletionsWithInsertText */
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @jsx: preserve
4+
5+
////const x: "a" | "b" = "/*openQuote*/"/*closeQuote*/;
6+
////const y: 'a' | 'b' = '/*openSingleQuote*/'/*closeSingleQuote*/;
7+
////const z: 'a' | 'b' = `/*openTemplate*/`/*closeTemplate*/;
8+
////const q: "`a`" | "`b`" = "`/*openTemplateInQuote*/a`/*closeTemplateInQuote*/";
9+
10+
////// "/*quoteInComment*/ </*lessInComment*/
11+
12+
// @Filename: /a.tsx
13+
////declare namespace JSX {
14+
//// interface Element {}
15+
//// interface IntrinsicElements {
16+
//// div: {};
17+
//// }
18+
////}
19+
////const ctr = </*openTag*/
20+
////const less = 1 </*lessThan*/
21+
22+
verify.completionsAt("openQuote", ["a", "b"], { triggerCharacter: '"' });
23+
verify.completionsAt("closeQuote", undefined, { triggerCharacter: '"' });
24+
25+
verify.completionsAt("openSingleQuote", ["a", "b"], { triggerCharacter: "'" });
26+
verify.completionsAt("closeSingleQuote", undefined, { triggerCharacter: "'" });
27+
28+
verify.completionsAt("openTemplate", ["a", "b"], { triggerCharacter: "`" });
29+
verify.completionsAt("closeTemplate", undefined, { triggerCharacter: "`" });
30+
31+
verify.completionsAt("openTemplateInQuote", undefined, { triggerCharacter: '`' });
32+
verify.completionsAt("closeTemplateInQuote", undefined, { triggerCharacter: '`' });
33+
34+
verify.completionsAt("quoteInComment", undefined, { triggerCharacter: '"' });
35+
verify.completionsAt("lessInComment", undefined, { triggerCharacter: "<" });
36+
37+
goTo.marker("openTag");
38+
verify.completionListContains("div", undefined, undefined, undefined, undefined, undefined, { triggerCharacter: "<" });
39+
verify.completionsAt("lessThan", undefined, { triggerCharacter: "<" });

tests/cases/fourslash/fourslash.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ declare namespace FourSlashInterface {
157157
spanIndex?: number,
158158
hasAction?: boolean,
159159
options?: UserPreferences & {
160+
triggerCharacter?: string,
160161
sourceDisplay?: string,
161162
isRecommended?: true,
162163
insertText?: string,
@@ -530,6 +531,7 @@ declare namespace FourSlashInterface {
530531
importModuleSpecifierPreference?: "relative" | "non-relative";
531532
}
532533
interface CompletionsAtOptions extends UserPreferences {
534+
triggerCharacter?: string;
533535
isNewIdentifierLocation?: boolean;
534536
}
535537
}

0 commit comments

Comments
 (0)