Account for trigger reasons, as implemented by Roslyn.

This commit is contained in:
Daniel Rosenwasser
2018-07-02 16:04:20 -07:00
parent c046958752
commit 9651c4231e
6 changed files with 175 additions and 42 deletions

View File

@@ -1426,17 +1426,22 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyNoSignatureHelp(triggerCharacter: ts.SignatureHelpTriggerCharacter | undefined, markers: ReadonlyArray<string>) {
public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray<string>) {
if (markers.length) {
for (const marker of markers) {
this.goToMarker(marker);
this.verifyNoSignatureHelp(triggerCharacter, ts.emptyArray);
this.verifySignatureHelpPresence(expectPresent, triggerReason, ts.emptyArray);
}
return;
}
const actual = this.getSignatureHelp({ triggerCharacter });
if (actual) {
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
const actual = this.getSignatureHelp({ triggerReason });
if (expectPresent !== !!actual) {
if (actual) {
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
}
else {
this.raiseError("Expected signature help, but none was returned.")
}
}
}
@@ -1455,7 +1460,7 @@ Actual: ${stringify(fullActual)}`);
}
private verifySignatureHelpWorker(options: FourSlashInterface.VerifySignatureHelpOptions) {
const help = this.getSignatureHelp({ triggerCharacter: options.triggerCharacter })!;
const help = this.getSignatureHelp({ triggerReason: options.triggerReason })!;
const selectedItem = help.items[help.selectedItemIndex];
// Argument index may exceed number of parameters
const currentParameter = selectedItem.parameters[help.argumentIndex] as ts.SignatureHelpParameter | undefined;
@@ -1497,7 +1502,7 @@ Actual: ${stringify(fullActual)}`);
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [
"marker",
"triggerCharacter",
"triggerReason",
"overloadsCount",
"docComment",
"text",
@@ -1769,9 +1774,9 @@ Actual: ${stringify(fullActual)}`);
Harness.IO.log(stringify(help.items[help.selectedItemIndex]));
}
private getSignatureHelp({ triggerCharacter }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, {
triggerCharacter
triggerReason
});
}
@@ -1870,7 +1875,12 @@ Actual: ${stringify(fullActual)}`);
if (highFidelity) {
if (ch === "(" || ch === "," || ch === "<") {
/* Signature help*/
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset, { triggerCharacter: ch });
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset, {
triggerReason: {
kind: "characterTyped",
triggerCharacter: ch
}
});
}
else if (prevChar === " " && /A-Za-z_/.test(ch)) {
/* Completions */
@@ -4081,11 +4091,15 @@ namespace FourSlashInterface {
}
public noSignatureHelp(...markers: string[]): void {
this.state.verifyNoSignatureHelp(/*triggerCharacter*/ undefined, markers);
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers);
}
public noSignatureHelpForTriggerCharacter(triggerCharacter: ts.SignatureHelpTriggerCharacter, ...markers: string[]): void {
this.state.verifyNoSignatureHelp(triggerCharacter, markers);
public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void {
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers);
}
public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void {
this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers);
}
public signatureHelp(...options: VerifySignatureHelpOptions[]): void {
@@ -4816,7 +4830,7 @@ namespace FourSlashInterface {
readonly isVariadic?: boolean;
/** @default ts.emptyArray */
readonly tags?: ReadonlyArray<ts.JSDocTagInfo>;
readonly triggerCharacter?: ts.SignatureHelpTriggerCharacter;
readonly triggerReason?: ts.SignatureHelpTriggerReason;
}
export type ArrayOrSingle<T> = T | ReadonlyArray<T>;

View File

@@ -2067,16 +2067,57 @@ namespace ts.server.protocol {
}
export type SignatureHelpTriggerCharacter = "," | "(" | "<";
export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
/**
* Arguments of a signature help request.
*/
/**
* Arguments of a signature help request.
*/
export interface SignatureHelpRequestArgs extends FileLocationRequestArgs {
/**
* Character that was responsible for triggering signature help.
* Should be `undefined` if a user manually requested completion.
* Reason why signature help was invoked.
* See each individual possible
*/
triggerCharacter?: SignatureHelpTriggerCharacter;
triggerReason?: SignatureHelpTriggerReason;
}
export type SignatureHelpTriggerReason =
| SignatureHelpInvokedReason
| SignatureHelpCharacterTypedReason
| SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
export interface SignatureHelpInvokedReason {
kind: "invoked",
triggerCharacter?: undefined,
}
/**
* Signals that the signature help request came from a user typing a character.
* Depending on the character and the syntactic context, the request may or may not be served a result.
*/
export interface SignatureHelpCharacterTypedReason {
kind: "characterTyped",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter: SignatureHelpTriggerCharacter,
}
/**
* Signals that this signature help request came from typing a character or moving the cursor.
* This should only occur if a signature help session was already active and the editor needs to see if it should adjust.
* The language service will unconditionally attempt to provide a result.
* `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move.
*/
export interface SignatureHelpRetriggeredReason {
kind: "retrigger",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpRetriggerCharacter,
}
/**

View File

@@ -1756,12 +1756,12 @@ namespace ts {
/**
* This is a semantic operation.
*/
function getSignatureHelpItems(fileName: string, position: number, { triggerCharacter }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerCharacter, cancellationToken);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken);
}
/// Syntactic features

View File

@@ -19,7 +19,7 @@ namespace ts.SignatureHelp {
argumentCount: number;
}
export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerCharacter: SignatureHelpTriggerCharacter | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
const typeChecker = program.getTypeChecker();
// Decide whether to show signature help
@@ -29,9 +29,11 @@ namespace ts.SignatureHelp {
return undefined;
}
// In the middle of a string, don't provide signature help unless the user explicitly requested it.
if (triggerCharacter !== undefined && isInString(sourceFile, position, startingToken)) {
return undefined;
if (shouldCarefullyCheckContext(triggerReason)) {
// In the middle of a string, don't provide signature help unless the user explicitly requested it.
if (isInString(sourceFile, position, startingToken)) {
return undefined;
}
}
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile);
@@ -55,6 +57,11 @@ namespace ts.SignatureHelp {
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker));
}
function shouldCarefullyCheckContext(reason: SignatureHelpTriggerReason | undefined) {
// Only need to be careful if the user typed a character and signature help wasn't showing.
return !!reason && reason.kind === "characterTyped";
}
function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray<Signature>, readonly resolvedSignature: Signature } | undefined {
const { invocation } = argumentInfo;
if (invocation.kind === InvocationKind.Call) {

View File

@@ -383,13 +383,50 @@ namespace ts {
}
export type SignatureHelpTriggerCharacter = "," | "(" | "<";
export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
export interface SignatureHelpItemsOptions {
triggerReason?: SignatureHelpTriggerReason;
}
export type SignatureHelpTriggerReason =
| SignatureHelpInvokedReason
| SignatureHelpCharacterTypedReason
| SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
export interface SignatureHelpInvokedReason {
kind: "invoked",
triggerCharacter?: undefined,
}
/**
* Signals that the signature help request came from a user typing a character.
* Depending on the character and the syntactic context, the request may or may not be served a result.
*/
export interface SignatureHelpCharacterTypedReason {
kind: "characterTyped",
/**
* If the editor is asking for signature help because a certain character was typed
* (as opposed to when the user explicitly requested them) this should be set.
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpTriggerCharacter;
triggerCharacter: SignatureHelpTriggerCharacter,
}
/**
* Signals that this signature help request came from typing a character or moving the cursor.
* This should only occur if a signature help session was already active and the editor needs to see if it should adjust.
* The language service will unconditionally attempt to provide a result.
* `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move.
*/
export interface SignatureHelpRetriggeredReason {
kind: "retrigger",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpRetriggerCharacter,
}
export interface ApplyCodeActionCommandResult {