Merge pull request #24915 from Microsoft/triggerSignatureHelpIArdlyKnowSignatureHelp

Trigger characters in signature help
This commit is contained in:
Daniel Rosenwasser 2018-07-02 23:36:57 -07:00 committed by GitHub
commit e13fd0c568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 371 additions and 79 deletions

View file

@ -1426,18 +1426,22 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyNoSignatureHelp(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(ts.emptyArray);
this.verifySignatureHelpPresence(expectPresent, triggerReason, ts.emptyArray);
}
return;
}
const actual = this.getSignatureHelp();
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.");
}
}
}
@ -1456,7 +1460,7 @@ Actual: ${stringify(fullActual)}`);
}
private verifySignatureHelpWorker(options: FourSlashInterface.VerifySignatureHelpOptions) {
const help = this.getSignatureHelp()!;
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;
@ -1498,6 +1502,7 @@ Actual: ${stringify(fullActual)}`);
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [
"marker",
"triggerReason",
"overloadsCount",
"docComment",
"text",
@ -1724,7 +1729,7 @@ Actual: ${stringify(fullActual)}`);
}
public printCurrentParameterHelp() {
const help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
const help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, /*options*/ undefined);
Harness.IO.log(stringify(help));
}
@ -1765,12 +1770,14 @@ Actual: ${stringify(fullActual)}`);
}
public printCurrentSignatureHelp() {
const help = this.getSignatureHelp()!;
const help = this.getSignatureHelp(ts.emptyOptions)!;
Harness.IO.log(stringify(help.items[help.selectedItemIndex]));
}
private getSignatureHelp(): ts.SignatureHelpItems | undefined {
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, {
triggerReason
});
}
public printCompletionListMembers(preferences: ts.UserPreferences | undefined) {
@ -1866,13 +1873,18 @@ Actual: ${stringify(fullActual)}`);
offset++;
if (highFidelity) {
if (ch === "(" || ch === ",") {
if (ch === "(" || ch === "," || ch === "<") {
/* Signature help*/
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset);
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset, {
triggerReason: {
kind: "characterTyped",
triggerCharacter: ch
}
});
}
else if (prevChar === " " && /A-Za-z_/.test(ch)) {
/* Completions */
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, ts.defaultPreferences);
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, ts.emptyOptions);
}
if (i % checkCadence === 0) {
@ -2505,7 +2517,7 @@ Actual: ${stringify(fullActual)}`);
`Expected '${fixId}'. Available action ids: ${ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.fixId)}`);
ts.Debug.assertEqual(fixWithId!.fixAllDescription, fixAllDescription);
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.defaultPreferences);
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.emptyOptions);
assert.deepEqual<ReadonlyArray<{}> | undefined>(commands, expectedCommands);
assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files");
this.applyChanges(changes);
@ -2585,7 +2597,7 @@ Actual: ${stringify(fullActual)}`);
* Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found.
* @param fileName Path to file where error should be retrieved from.
*/
private getCodeFixes(fileName: string, errorCode?: number, preferences: ts.UserPreferences = ts.defaultPreferences): ts.CodeFixAction[] {
private getCodeFixes(fileName: string, errorCode?: number, preferences: ts.UserPreferences = ts.emptyOptions): ts.CodeFixAction[] {
const diagnosticsForCodeFix = this.getDiagnostics(fileName, /*includeSuggestions*/ true).map(diagnostic => ({
start: diagnostic.start,
length: diagnostic.length,
@ -3030,7 +3042,7 @@ Actual: ${stringify(fullActual)}`);
this.raiseError(`Expected action description to be ${JSON.stringify(actionDescription)}, got: ${JSON.stringify(action.description)}`);
}
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.defaultPreferences)!;
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.emptyOptions)!;
for (const edit of editInfo.edits) {
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
}
@ -3094,7 +3106,7 @@ Actual: ${stringify(fullActual)}`);
const action = ts.first(refactor.actions);
assert(action.name === "Move to a new file" && action.description === "Move to a new file");
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.defaultPreferences)!;
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.emptyOptions)!;
this.testNewFileContents(editInfo.edits, options.newFileContents, "move to new file");
}
@ -3136,14 +3148,14 @@ Actual: ${stringify(fullActual)}`);
formattingOptions = formattingOptions || this.formatCodeSettings;
const markerPos = this.getMarkerByName(markerName).position;
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos, ts.defaultPreferences);
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos, ts.emptyOptions);
const applicableRefactorToApply = ts.find(applicableRefactors, refactor => refactor.name === refactorNameToApply);
if (!applicableRefactorToApply) {
this.raiseError(`The expected refactor: ${refactorNameToApply} is not available at the marker location.`);
}
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName, ts.defaultPreferences)!;
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName, ts.emptyOptions)!;
for (const edit of editInfo.edits) {
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
@ -3340,7 +3352,7 @@ Actual: ${stringify(fullActual)}`);
public getEditsForFileRename({ oldPath, newPath, newFileContents }: FourSlashInterface.GetEditsForFileRenameOptions): void {
const test = (fileContents: { readonly [fileName: string]: string }, description: string): void => {
const changes = this.languageService.getEditsForFileRename(oldPath, newPath, this.formatCodeSettings, ts.defaultPreferences);
const changes = this.languageService.getEditsForFileRename(oldPath, newPath, this.formatCodeSettings, ts.emptyOptions);
this.testNewFileContents(changes, fileContents, description);
};
@ -3354,7 +3366,7 @@ Actual: ${stringify(fullActual)}`);
test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved");
}
private getApplicableRefactors(positionOrRange: number | ts.TextRange, preferences = ts.defaultPreferences): ReadonlyArray<ts.ApplicableRefactorInfo> {
private getApplicableRefactors(positionOrRange: number | ts.TextRange, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.languageService.getApplicableRefactors(this.activeFile.fileName, positionOrRange, preferences) || ts.emptyArray;
}
}
@ -4042,7 +4054,15 @@ namespace FourSlashInterface {
}
public noSignatureHelp(...markers: string[]): void {
this.state.verifyNoSignatureHelp(markers);
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, 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 {
@ -4757,6 +4777,7 @@ namespace FourSlashInterface {
readonly isVariadic?: boolean;
/** @default ts.emptyArray */
readonly tags?: ReadonlyArray<ts.JSDocTagInfo>;
readonly triggerReason?: ts.SignatureHelpTriggerReason;
}
export interface VerifyNavigateToOptions {

View file

@ -449,8 +449,8 @@ namespace Harness.LanguageService {
getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan {
return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position));
}
getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems {
return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position));
getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems {
return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position, options));
}
getRenameInfo(fileName: string, position: number): ts.RenameInfo {
return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position));

View file

@ -477,7 +477,7 @@ namespace ts.server {
this.hostConfiguration = {
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
preferences: defaultPreferences,
preferences: emptyOptions,
hostInfo: "Unknown host",
extraFileExtensions: []
};

View file

@ -1789,6 +1789,10 @@ namespace ts.server.protocol {
* Optional prefix to apply to possible completions.
*/
prefix?: string;
/**
* Character that was responsible for triggering completion.
* Should be `undefined` if a user manually requested completion.
*/
triggerCharacter?: CompletionsTriggerCharacter;
/**
* @deprecated Use UserPreferences.includeCompletionsForModuleExports
@ -2062,10 +2066,58 @@ namespace ts.server.protocol {
argumentCount: number;
}
/**
* Arguments of a signature help request.
*/
export type SignatureHelpTriggerCharacter = "," | "(" | "<";
export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
/**
* Arguments of a signature help request.
*/
export interface SignatureHelpRequestArgs extends FileLocationRequestArgs {
/**
* Reason why signature help was invoked.
* See each individual possible
*/
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

@ -432,7 +432,7 @@ namespace ts.server {
if (preferences) {
if (!this.preferences) {
this.preferences = defaultPreferences;
this.preferences = emptyOptions;
}
this.preferences = { ...this.preferences, ...preferences };
}

View file

@ -1434,7 +1434,7 @@ namespace ts.server {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
const position = this.getPosition(args, scriptInfo);
const helpItems = project.getLanguageService().getSignatureHelpItems(file, position);
const helpItems = project.getLanguageService().getSignatureHelpItems(file, position, args);
if (!helpItems) {
return undefined;
}

View file

@ -1396,7 +1396,7 @@ namespace ts {
return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)];
}
function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = defaultPreferences): CompletionInfo | undefined {
function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions): CompletionInfo | undefined {
// Convert from deprecated options names to new names
const fullPreferences: UserPreferences = {
...identity<UserPreferences>(options), // avoid excess property check
@ -1414,7 +1414,7 @@ namespace ts {
options.triggerCharacter);
}
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = defaultPreferences): CompletionEntryDetails | undefined {
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions): CompletionEntryDetails | undefined {
synchronizeHostData();
return Completions.getCompletionEntryDetails(
program,
@ -1756,12 +1756,12 @@ namespace ts {
/**
* This is a semantic operation.
*/
function getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined {
function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, cancellationToken);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken);
}
/// Syntactic features
@ -1940,7 +1940,7 @@ namespace ts {
return [];
}
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings, preferences: UserPreferences = defaultPreferences): ReadonlyArray<CodeFixAction> {
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): ReadonlyArray<CodeFixAction> {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const span = createTextSpanFromBounds(start, end);
@ -1952,7 +1952,7 @@ namespace ts {
});
}
function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = defaultPreferences): CombinedCodeActions {
function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions {
synchronizeHostData();
Debug.assert(scope.type === "file");
const sourceFile = getValidSourceFile(scope.fileName);
@ -1961,7 +1961,7 @@ namespace ts {
return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences });
}
function organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences = defaultPreferences): ReadonlyArray<FileTextChanges> {
function organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): ReadonlyArray<FileTextChanges> {
synchronizeHostData();
Debug.assert(scope.type === "file");
const sourceFile = getValidSourceFile(scope.fileName);
@ -1970,7 +1970,7 @@ namespace ts {
return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences);
}
function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = defaultPreferences): ReadonlyArray<FileTextChanges> {
function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): ReadonlyArray<FileTextChanges> {
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences);
}
@ -2219,7 +2219,7 @@ namespace ts {
};
}
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = defaultPreferences): ApplicableRefactorInfo[] {
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions): ApplicableRefactorInfo[] {
synchronizeHostData();
const file = getValidSourceFile(fileName);
return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences));
@ -2231,7 +2231,7 @@ namespace ts {
positionOrRange: number | TextRange,
refactorName: string,
actionName: string,
preferences: UserPreferences = defaultPreferences,
preferences: UserPreferences = emptyOptions,
): RefactorEditInfo | undefined {
synchronizeHostData();
const file = getValidSourceFile(fileName);

View file

@ -158,7 +158,7 @@ namespace ts {
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string;
getBreakpointStatementAtPosition(fileName: string, position: number): string;
getSignatureHelpItems(fileName: string, position: number): string;
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string;
/**
* Returns a JSON-encoded value of the type:
@ -769,10 +769,10 @@ namespace ts {
/// SIGNATUREHELP
public getSignatureHelpItems(fileName: string, position: number): string {
public getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string {
return this.forwardJSONCall(
`getSignatureHelpItems('${fileName}', ${position})`,
() => this.languageService.getSignatureHelpItems(fileName, position)
() => this.languageService.getSignatureHelpItems(fileName, position, options)
);
}

View file

@ -19,7 +19,7 @@ namespace ts.SignatureHelp {
argumentCount: number;
}
export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, 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,6 +29,13 @@ namespace ts.SignatureHelp {
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);
if (!argumentInfo) return undefined;
@ -50,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

@ -242,7 +242,7 @@ namespace ts {
readonly allowTextChangesInNewFiles?: boolean;
}
/* @internal */
export const defaultPreferences: UserPreferences = {};
export const emptyOptions = {};
//
// Public services of a language service instance associated
@ -292,7 +292,7 @@ namespace ts {
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined;
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined;
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined;
getRenameInfo(fileName: string, position: number): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] | undefined;
@ -371,7 +371,10 @@ namespace ts {
export type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<";
export interface GetCompletionsAtPositionOptions extends UserPreferences {
/** 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. */
/**
* If the editor is asking for completions because a certain character was typed
* (as opposed to when the user explicitly requested them) this should be set.
*/
triggerCharacter?: CompletionsTriggerCharacter;
/** @deprecated Use includeCompletionsForModuleExports */
includeExternalModuleExports?: boolean;
@ -379,6 +382,53 @@ namespace ts {
includeInsertTextCompletions?: boolean;
}
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";
/**
* 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;
}
export interface ApplyCodeActionCommandResult {
successMessage: string;
}

View file

@ -8,7 +8,7 @@ namespace ts {
`;
it("can cancel signature help mid-request", () => {
verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type
service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"))!,
service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"), emptyOptions)!,
r => assert.exists(r.items[0])
);
});

View file

@ -124,7 +124,7 @@ namespace ts {
endPosition: selectionRange.end,
host: notImplementedHost,
formatContext: formatting.getFormatContext(testFormatOptions),
preferences: defaultPreferences,
preferences: emptyOptions,
};
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);
@ -188,7 +188,7 @@ namespace ts {
endPosition: selectionRange.end,
host: notImplementedHost,
formatContext: formatting.getFormatContext(testFormatOptions),
preferences: defaultPreferences,
preferences: emptyOptions,
};
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);

View file

@ -270,7 +270,7 @@ export const Other = 1;
content: "function F() { }",
};
const languageService = makeLanguageService(testFile);
const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatOptions, defaultPreferences);
const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatOptions, emptyOptions);
assert.isEmpty(changes);
});
@ -741,7 +741,7 @@ export * from "lib";
function runBaseline(baselinePath: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
const { path: testPath, content: testContent } = testFile;
const languageService = makeLanguageService(testFile, ...otherFiles);
const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatOptions, defaultPreferences);
const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatOptions, emptyOptions);
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, testPath);

View file

@ -1525,13 +1525,13 @@ namespace ts.projectSystem {
service.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, defaultPreferences)!;
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!;
// should contain completions for string
assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'");
assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'");
service.closeClientFile(f2.path);
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, defaultPreferences)!;
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!;
// should contain completions for string
assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'");
assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'");
@ -1557,11 +1557,11 @@ namespace ts.projectSystem {
service.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, defaultPreferences)!;
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!;
assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'");
service.closeClientFile(f2.path);
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, defaultPreferences)!;
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!;
assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'");
const sf2 = service.externalProjects[0].getLanguageService().getProgram()!.getSourceFile(f2.path)!;
assert.equal(sf2.text, "");
@ -2201,7 +2201,7 @@ namespace ts.projectSystem {
// Check identifiers defined in HTML content are available in .ts file
const project = configuredProjectAt(projectService, 0);
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, defaultPreferences);
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions);
assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
// Close HTML file
@ -2215,7 +2215,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
// Check identifiers defined in HTML content are not available in .ts file
completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, defaultPreferences);
completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, emptyOptions);
assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`);
});
@ -8701,7 +8701,7 @@ export const x = 10;`
Debug.assert(!!project.resolveModuleNames);
const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatOptions, defaultPreferences);
const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatOptions, emptyOptions);
assert.deepEqual<ReadonlyArray<FileTextChanges>>(edits, [{
fileName: "/user.ts",
textChanges: [{

View file

@ -9963,7 +9963,7 @@ declare namespace ts {
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
readonly allowTextChangesInNewFiles?: boolean;
}
const defaultPreferences: UserPreferences;
const emptyOptions: {};
interface LanguageService {
cleanupSemanticCache(): void;
getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[];
@ -9987,7 +9987,7 @@ declare namespace ts {
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined;
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined;
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined;
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined;
getRenameInfo(fileName: string, position: number): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] | undefined;
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined;
@ -10048,13 +10048,54 @@ declare namespace ts {
type OrganizeImportsScope = CombinedCodeFixScope;
type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<";
interface GetCompletionsAtPositionOptions extends UserPreferences {
/** 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. */
/**
* If the editor is asking for completions because a certain character was typed
* (as opposed to when the user explicitly requested them) this should be set.
*/
triggerCharacter?: CompletionsTriggerCharacter;
/** @deprecated Use includeCompletionsForModuleExports */
includeExternalModuleExports?: boolean;
/** @deprecated Use includeCompletionsWithInsertText */
includeInsertTextCompletions?: boolean;
}
type SignatureHelpTriggerCharacter = "," | "(" | "<";
type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
interface SignatureHelpItemsOptions {
triggerReason?: SignatureHelpTriggerReason;
}
type SignatureHelpTriggerReason = SignatureHelpInvokedReason | SignatureHelpCharacterTypedReason | SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
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.
*/
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.
*/
interface SignatureHelpRetriggeredReason {
kind: "retrigger";
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpRetriggerCharacter;
}
interface ApplyCodeActionCommandResult {
successMessage: string;
}
@ -11247,7 +11288,7 @@ declare namespace ts.Rename {
function getRenameInfo(program: Program, sourceFile: SourceFile, position: number): RenameInfo;
}
declare namespace ts.SignatureHelp {
function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, cancellationToken: CancellationToken): SignatureHelpItems | undefined;
function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined;
interface ArgumentInfoForCompletions {
readonly invocation: CallLikeExpression;
readonly argumentIndex: number;
@ -11944,7 +11985,7 @@ declare namespace ts {
getQuickInfoAtPosition(fileName: string, position: number): string;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string;
getBreakpointStatementAtPosition(fileName: string, position: number): string;
getSignatureHelpItems(fileName: string, position: number): string;
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string;
/**
* Returns a JSON-encoded value of the type:
* { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } }
@ -12979,7 +13020,23 @@ declare namespace ts.server.protocol {
argumentIndex: number;
argumentCount: number;
}
type SignatureHelpTriggerCharacter = "," | "(" | "<";
type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
interface SignatureHelpRequestArgs extends FileLocationRequestArgs {
triggerReason?: SignatureHelpTriggerReason;
}
type SignatureHelpTriggerReason = SignatureHelpInvokedReason | SignatureHelpCharacterTypedReason | SignatureHelpRetriggeredReason;
interface SignatureHelpInvokedReason {
kind: "invoked";
triggerCharacter?: undefined;
}
interface SignatureHelpCharacterTypedReason {
kind: "characterTyped";
triggerCharacter: SignatureHelpTriggerCharacter;
}
interface SignatureHelpRetriggeredReason {
kind: "retrigger";
triggerCharacter?: SignatureHelpRetriggerCharacter;
}
interface SignatureHelpRequest extends FileLocationRequest {
command: CommandTypes.SignatureHelp;

View file

@ -4740,7 +4740,7 @@ declare namespace ts {
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined;
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined;
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined;
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined;
getRenameInfo(fileName: string, position: number): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] | undefined;
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined;
@ -4800,13 +4800,54 @@ declare namespace ts {
type OrganizeImportsScope = CombinedCodeFixScope;
type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<";
interface GetCompletionsAtPositionOptions extends UserPreferences {
/** 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. */
/**
* If the editor is asking for completions because a certain character was typed
* (as opposed to when the user explicitly requested them) this should be set.
*/
triggerCharacter?: CompletionsTriggerCharacter;
/** @deprecated Use includeCompletionsForModuleExports */
includeExternalModuleExports?: boolean;
/** @deprecated Use includeCompletionsWithInsertText */
includeInsertTextCompletions?: boolean;
}
type SignatureHelpTriggerCharacter = "," | "(" | "<";
type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
interface SignatureHelpItemsOptions {
triggerReason?: SignatureHelpTriggerReason;
}
type SignatureHelpTriggerReason = SignatureHelpInvokedReason | SignatureHelpCharacterTypedReason | SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
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.
*/
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.
*/
interface SignatureHelpRetriggeredReason {
kind: "retrigger";
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpRetriggerCharacter;
}
interface ApplyCodeActionCommandResult {
successMessage: string;
}

View file

@ -139,7 +139,6 @@ declare namespace FourSlashInterface {
file(name: string, content?: string, scriptKindName?: string): any;
select(startMarker: string, endMarker: string): void;
selectRange(range: Range): void;
selectAllInFile(fileName: string): void;
}
class verifyNegatable {
private negative;
@ -179,7 +178,7 @@ declare namespace FourSlashInterface {
isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void;
codeFix(options: {
description: string,
newFileContent?: NewFileContent,
newFileContent?: string | { readonly [fileName: string]: string },
newRangeContent?: string,
errorCode?: number,
index?: number,
@ -191,7 +190,6 @@ declare namespace FourSlashInterface {
applicableRefactorAvailableForRange(): void;
refactorAvailable(name: string, actionName?: string): void;
refactorsAvailable(names: ReadonlyArray<string>): void;
refactor(options: {
name: string;
actionName: string;
@ -257,14 +255,16 @@ declare namespace FourSlashInterface {
* For each of starts, asserts the ranges that are referenced from there.
* This uses the 'findReferences' command instead of 'getReferencesAtPosition', so references are grouped by their definition.
*/
referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<Range>, parts: Array<FourSlashInterface.ReferenceGroup>): void;
referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<Range>, parts: Array<{ definition: ReferencesDefinition, ranges: Range[] }>): void;
singleReferenceGroup(definition: ReferencesDefinition, ranges?: Range[]): void;
rangesAreOccurrences(isWriteAccess?: boolean): void;
rangesWithSameTextAreRenameLocations(): void;
rangesAreRenameLocations(options?: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: Range[] });
findReferencesDefinitionDisplayPartsAtCaretAre(expected: ts.SymbolDisplayPart[]): void;
noSignatureHelp(...markers: string[]): void;
signatureHelp(...options: VerifySignatureHelpOptions[]): void;
noSignatureHelpForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: string[]): void
signatureHelpPresentForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: string[]): void
signatureHelp(...options: VerifySignatureHelpOptions[], ): void;
// Checks that there are no compile errors.
noErrors(): void;
numberOfErrorsInCurrentFile(expected: number): void;
@ -336,7 +336,7 @@ declare namespace FourSlashInterface {
getEditsForFileRename(options: {
oldPath: string;
newPath: string;
newFileContents: { readonly [fileName: string]: string };
newFileContents: { [fileName: string]: string };
}): void;
moveToNewFile(options: {
readonly newFileContents: { readonly [fileName: string]: string };
@ -357,7 +357,7 @@ declare namespace FourSlashInterface {
enableFormatting(): void;
disableFormatting(): void;
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: NewFileContent }): void;
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: string }): void;
}
class debug {
printCurrentParameterHelp(): void;
@ -512,10 +512,6 @@ declare namespace FourSlashInterface {
text: string;
range: Range;
}
interface ReferenceGroup {
readonly definition: ReferencesDefinition;
readonly ranges: ReadonlyArray<Range>;
}
interface Diagnostic {
message: string;
/** @default `test.ranges()[0]` */
@ -565,6 +561,47 @@ declare namespace FourSlashInterface {
argumentCount?: number;
isVariadic?: boolean;
tags?: ReadonlyArray<JSDocTagInfo>;
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: string,
}
/**
* 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?: string,
}
interface VerifyNavigateToOptions {
@ -589,7 +626,6 @@ declare namespace FourSlashInterface {
}
type ArrayOrSingle<T> = T | ReadonlyArray<T>;
type NewFileContent = string | { readonly [fileName: string]: string };
}
declare function verifyOperationIsCancelled(f: any): void;
declare var test: FourSlashInterface.test_;

View file

@ -0,0 +1,23 @@
/// <reference path="fourslash.ts" />
////function foo<T>(x: T): T {
//// throw null;
////}
////
////foo("/**/")
goTo.marker();
for (const triggerCharacter of ["<", "(", ","]) {
edit.insert(triggerCharacter);
verify.noSignatureHelpForTriggerReason({
kind: "characterTyped",
triggerCharacter,
});
verify.signatureHelpPresentForTriggerReason({
kind: "retrigger",
triggerCharacter,
});
edit.backspace();
}
verify.signatureHelpPresentForTriggerReason(/*triggerReason*/ undefined);
verify.signatureHelpPresentForTriggerReason({ kind: "invoked" });