Skip to content

Commit e13fd0c

Browse files
Merge pull request #24915 from Microsoft/triggerSignatureHelpIArdlyKnowSignatureHelp
Trigger characters in signature help
2 parents efc1b7d + e56a5c1 commit e13fd0c

18 files changed

+370
-78
lines changed

src/harness/fourslash.ts

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,18 +1426,22 @@ Actual: ${stringify(fullActual)}`);
14261426
}
14271427
}
14281428

1429-
public verifyNoSignatureHelp(markers: ReadonlyArray<string>) {
1429+
public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray<string>) {
14301430
if (markers.length) {
14311431
for (const marker of markers) {
14321432
this.goToMarker(marker);
1433-
this.verifyNoSignatureHelp(ts.emptyArray);
1433+
this.verifySignatureHelpPresence(expectPresent, triggerReason, ts.emptyArray);
14341434
}
14351435
return;
14361436
}
1437-
1438-
const actual = this.getSignatureHelp();
1439-
if (actual) {
1440-
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
1437+
const actual = this.getSignatureHelp({ triggerReason });
1438+
if (expectPresent !== !!actual) {
1439+
if (actual) {
1440+
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
1441+
}
1442+
else {
1443+
this.raiseError("Expected signature help, but none was returned.");
1444+
}
14411445
}
14421446
}
14431447

@@ -1456,7 +1460,7 @@ Actual: ${stringify(fullActual)}`);
14561460
}
14571461

14581462
private verifySignatureHelpWorker(options: FourSlashInterface.VerifySignatureHelpOptions) {
1459-
const help = this.getSignatureHelp()!;
1463+
const help = this.getSignatureHelp({ triggerReason: options.triggerReason })!;
14601464
const selectedItem = help.items[help.selectedItemIndex];
14611465
// Argument index may exceed number of parameters
14621466
const currentParameter = selectedItem.parameters[help.argumentIndex] as ts.SignatureHelpParameter | undefined;
@@ -1498,6 +1502,7 @@ Actual: ${stringify(fullActual)}`);
14981502

14991503
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [
15001504
"marker",
1505+
"triggerReason",
15011506
"overloadsCount",
15021507
"docComment",
15031508
"text",
@@ -1724,7 +1729,7 @@ Actual: ${stringify(fullActual)}`);
17241729
}
17251730

17261731
public printCurrentParameterHelp() {
1727-
const help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
1732+
const help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, /*options*/ undefined);
17281733
Harness.IO.log(stringify(help));
17291734
}
17301735

@@ -1765,12 +1770,14 @@ Actual: ${stringify(fullActual)}`);
17651770
}
17661771

17671772
public printCurrentSignatureHelp() {
1768-
const help = this.getSignatureHelp()!;
1773+
const help = this.getSignatureHelp(ts.emptyOptions)!;
17691774
Harness.IO.log(stringify(help.items[help.selectedItemIndex]));
17701775
}
17711776

1772-
private getSignatureHelp(): ts.SignatureHelpItems | undefined {
1773-
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
1777+
private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
1778+
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, {
1779+
triggerReason
1780+
});
17741781
}
17751782

17761783
public printCompletionListMembers(preferences: ts.UserPreferences | undefined) {
@@ -1866,13 +1873,18 @@ Actual: ${stringify(fullActual)}`);
18661873
offset++;
18671874

18681875
if (highFidelity) {
1869-
if (ch === "(" || ch === ",") {
1876+
if (ch === "(" || ch === "," || ch === "<") {
18701877
/* Signature help*/
1871-
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset);
1878+
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset, {
1879+
triggerReason: {
1880+
kind: "characterTyped",
1881+
triggerCharacter: ch
1882+
}
1883+
});
18721884
}
18731885
else if (prevChar === " " && /A-Za-z_/.test(ch)) {
18741886
/* Completions */
1875-
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, ts.defaultPreferences);
1887+
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, ts.emptyOptions);
18761888
}
18771889

18781890
if (i % checkCadence === 0) {
@@ -2505,7 +2517,7 @@ Actual: ${stringify(fullActual)}`);
25052517
`Expected '${fixId}'. Available action ids: ${ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.fixId)}`);
25062518
ts.Debug.assertEqual(fixWithId!.fixAllDescription, fixAllDescription);
25072519

2508-
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.defaultPreferences);
2520+
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.emptyOptions);
25092521
assert.deepEqual<ReadonlyArray<{}> | undefined>(commands, expectedCommands);
25102522
assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files");
25112523
this.applyChanges(changes);
@@ -2585,7 +2597,7 @@ Actual: ${stringify(fullActual)}`);
25852597
* Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found.
25862598
* @param fileName Path to file where error should be retrieved from.
25872599
*/
2588-
private getCodeFixes(fileName: string, errorCode?: number, preferences: ts.UserPreferences = ts.defaultPreferences): ts.CodeFixAction[] {
2600+
private getCodeFixes(fileName: string, errorCode?: number, preferences: ts.UserPreferences = ts.emptyOptions): ts.CodeFixAction[] {
25892601
const diagnosticsForCodeFix = this.getDiagnostics(fileName, /*includeSuggestions*/ true).map(diagnostic => ({
25902602
start: diagnostic.start,
25912603
length: diagnostic.length,
@@ -3030,7 +3042,7 @@ Actual: ${stringify(fullActual)}`);
30303042
this.raiseError(`Expected action description to be ${JSON.stringify(actionDescription)}, got: ${JSON.stringify(action.description)}`);
30313043
}
30323044

3033-
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.defaultPreferences)!;
3045+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.emptyOptions)!;
30343046
for (const edit of editInfo.edits) {
30353047
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
30363048
}
@@ -3094,7 +3106,7 @@ Actual: ${stringify(fullActual)}`);
30943106
const action = ts.first(refactor.actions);
30953107
assert(action.name === "Move to a new file" && action.description === "Move to a new file");
30963108

3097-
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.defaultPreferences)!;
3109+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.emptyOptions)!;
30983110
this.testNewFileContents(editInfo.edits, options.newFileContents, "move to new file");
30993111
}
31003112

@@ -3136,14 +3148,14 @@ Actual: ${stringify(fullActual)}`);
31363148
formattingOptions = formattingOptions || this.formatCodeSettings;
31373149
const markerPos = this.getMarkerByName(markerName).position;
31383150

3139-
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos, ts.defaultPreferences);
3151+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos, ts.emptyOptions);
31403152
const applicableRefactorToApply = ts.find(applicableRefactors, refactor => refactor.name === refactorNameToApply);
31413153

31423154
if (!applicableRefactorToApply) {
31433155
this.raiseError(`The expected refactor: ${refactorNameToApply} is not available at the marker location.`);
31443156
}
31453157

3146-
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName, ts.defaultPreferences)!;
3158+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName, ts.emptyOptions)!;
31473159

31483160
for (const edit of editInfo.edits) {
31493161
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
@@ -3340,7 +3352,7 @@ Actual: ${stringify(fullActual)}`);
33403352

33413353
public getEditsForFileRename({ oldPath, newPath, newFileContents }: FourSlashInterface.GetEditsForFileRenameOptions): void {
33423354
const test = (fileContents: { readonly [fileName: string]: string }, description: string): void => {
3343-
const changes = this.languageService.getEditsForFileRename(oldPath, newPath, this.formatCodeSettings, ts.defaultPreferences);
3355+
const changes = this.languageService.getEditsForFileRename(oldPath, newPath, this.formatCodeSettings, ts.emptyOptions);
33443356
this.testNewFileContents(changes, fileContents, description);
33453357
};
33463358

@@ -3354,7 +3366,7 @@ Actual: ${stringify(fullActual)}`);
33543366
test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved");
33553367
}
33563368

3357-
private getApplicableRefactors(positionOrRange: number | ts.TextRange, preferences = ts.defaultPreferences): ReadonlyArray<ts.ApplicableRefactorInfo> {
3369+
private getApplicableRefactors(positionOrRange: number | ts.TextRange, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
33583370
return this.languageService.getApplicableRefactors(this.activeFile.fileName, positionOrRange, preferences) || ts.emptyArray;
33593371
}
33603372
}
@@ -4042,7 +4054,15 @@ namespace FourSlashInterface {
40424054
}
40434055

40444056
public noSignatureHelp(...markers: string[]): void {
4045-
this.state.verifyNoSignatureHelp(markers);
4057+
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers);
4058+
}
4059+
4060+
public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void {
4061+
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers);
4062+
}
4063+
4064+
public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void {
4065+
this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers);
40464066
}
40474067

40484068
public signatureHelp(...options: VerifySignatureHelpOptions[]): void {
@@ -4757,6 +4777,7 @@ namespace FourSlashInterface {
47574777
readonly isVariadic?: boolean;
47584778
/** @default ts.emptyArray */
47594779
readonly tags?: ReadonlyArray<ts.JSDocTagInfo>;
4780+
readonly triggerReason?: ts.SignatureHelpTriggerReason;
47604781
}
47614782

47624783
export interface VerifyNavigateToOptions {

src/harness/harnessLanguageService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@ namespace Harness.LanguageService {
449449
getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan {
450450
return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position));
451451
}
452-
getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems {
453-
return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position));
452+
getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems {
453+
return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position, options));
454454
}
455455
getRenameInfo(fileName: string, position: number): ts.RenameInfo {
456456
return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position));

src/server/editorServices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ namespace ts.server {
477477

478478
this.hostConfiguration = {
479479
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
480-
preferences: defaultPreferences,
480+
preferences: emptyOptions,
481481
hostInfo: "Unknown host",
482482
extraFileExtensions: []
483483
};

src/server/protocol.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,10 @@ namespace ts.server.protocol {
17891789
* Optional prefix to apply to possible completions.
17901790
*/
17911791
prefix?: string;
1792+
/**
1793+
* Character that was responsible for triggering completion.
1794+
* Should be `undefined` if a user manually requested completion.
1795+
*/
17921796
triggerCharacter?: CompletionsTriggerCharacter;
17931797
/**
17941798
* @deprecated Use UserPreferences.includeCompletionsForModuleExports
@@ -2062,10 +2066,58 @@ namespace ts.server.protocol {
20622066
argumentCount: number;
20632067
}
20642068

2069+
export type SignatureHelpTriggerCharacter = "," | "(" | "<";
2070+
export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
2071+
2072+
/**
2073+
* Arguments of a signature help request.
2074+
*/
2075+
export interface SignatureHelpRequestArgs extends FileLocationRequestArgs {
2076+
/**
2077+
* Reason why signature help was invoked.
2078+
* See each individual possible
2079+
*/
2080+
triggerReason?: SignatureHelpTriggerReason;
2081+
}
2082+
2083+
export type SignatureHelpTriggerReason =
2084+
| SignatureHelpInvokedReason
2085+
| SignatureHelpCharacterTypedReason
2086+
| SignatureHelpRetriggeredReason;
2087+
20652088
/**
2066-
* Arguments of a signature help request.
2089+
* Signals that the user manually requested signature help.
2090+
* The language service will unconditionally attempt to provide a result.
20672091
*/
2068-
export interface SignatureHelpRequestArgs extends FileLocationRequestArgs {
2092+
export interface SignatureHelpInvokedReason {
2093+
kind: "invoked";
2094+
triggerCharacter?: undefined;
2095+
}
2096+
2097+
/**
2098+
* Signals that the signature help request came from a user typing a character.
2099+
* Depending on the character and the syntactic context, the request may or may not be served a result.
2100+
*/
2101+
export interface SignatureHelpCharacterTypedReason {
2102+
kind: "characterTyped";
2103+
/**
2104+
* Character that was responsible for triggering signature help.
2105+
*/
2106+
triggerCharacter: SignatureHelpTriggerCharacter;
2107+
}
2108+
2109+
/**
2110+
* Signals that this signature help request came from typing a character or moving the cursor.
2111+
* This should only occur if a signature help session was already active and the editor needs to see if it should adjust.
2112+
* The language service will unconditionally attempt to provide a result.
2113+
* `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move.
2114+
*/
2115+
export interface SignatureHelpRetriggeredReason {
2116+
kind: "retrigger";
2117+
/**
2118+
* Character that was responsible for triggering signature help.
2119+
*/
2120+
triggerCharacter?: SignatureHelpRetriggerCharacter;
20692121
}
20702122

20712123
/**

src/server/scriptInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ namespace ts.server {
432432

433433
if (preferences) {
434434
if (!this.preferences) {
435-
this.preferences = defaultPreferences;
435+
this.preferences = emptyOptions;
436436
}
437437
this.preferences = { ...this.preferences, ...preferences };
438438
}

src/server/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ namespace ts.server {
14341434
const { file, project } = this.getFileAndProject(args);
14351435
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
14361436
const position = this.getPosition(args, scriptInfo);
1437-
const helpItems = project.getLanguageService().getSignatureHelpItems(file, position);
1437+
const helpItems = project.getLanguageService().getSignatureHelpItems(file, position, args);
14381438
if (!helpItems) {
14391439
return undefined;
14401440
}

0 commit comments

Comments
 (0)