Skip to content

Commit

Permalink
Add retrigger characters to SignatureHelpProvider
Browse files Browse the repository at this point in the history
Introduces the concept of a re-trigger character to the signature help provider. This is a seperate set of characters that are registered with the provider. Typing a retrigger character fires a new signature help request if signature help is already showing.

#54972
  • Loading branch information
mjbvz committed Oct 5, 2018
1 parent 2e8993b commit 2f51e97
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as typeConverters from '../utils/typeConverters';
class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {

public static readonly triggerCharacters = ['(', ',', '<'];
public static readonly retriggerCharacters = [')'];

public constructor(
private readonly client: ITypeScriptServiceClient
Expand Down Expand Up @@ -92,6 +93,8 @@ export function register(
client: ITypeScriptServiceClient,
) {
return vscode.languages.registerSignatureHelpProvider(selector,
new TypeScriptSignatureHelpProvider(client),
...TypeScriptSignatureHelpProvider.triggerCharacters);
new TypeScriptSignatureHelpProvider(client), {
triggerCharacters: TypeScriptSignatureHelpProvider.triggerCharacters,
retriggerCharacters: TypeScriptSignatureHelpProvider.retriggerCharacters
});
}
3 changes: 2 additions & 1 deletion src/vs/editor/common/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,8 @@ export interface SignatureHelpContext {
*/
export interface SignatureHelpProvider {

signatureHelpTriggerCharacters: string[];
readonly signatureHelpTriggerCharacters: ReadonlyArray<string>;
readonly signatureHelpRetriggerCharacters: ReadonlyArray<string>;

/**
* Provide help for the signature at the given position and document.
Expand Down
24 changes: 20 additions & 4 deletions src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class ParameterHintsModel extends Disposable {
private active: boolean = false;
private pending: boolean = false;
private triggerChars = new CharacterSet();
private retriggerChars = new CharacterSet();

private triggerContext: modes.SignatureHelpContext | undefined;
private throttledDelayer: RunOnceScheduler;
Expand Down Expand Up @@ -146,6 +147,7 @@ export class ParameterHintsModel extends Disposable {

// Update trigger characters
this.triggerChars = new CharacterSet();
this.retriggerChars = new CharacterSet();

const model = this.editor.getModel();
if (!model) {
Expand All @@ -156,6 +158,15 @@ export class ParameterHintsModel extends Disposable {
if (Array.isArray(support.signatureHelpTriggerCharacters)) {
for (const ch of support.signatureHelpTriggerCharacters) {
this.triggerChars.add(ch.charCodeAt(0));

// All trigger characters are also considered retrigger characters
this.retriggerChars.add(ch.charCodeAt(0));

}
}
if (Array.isArray(support.signatureHelpRetriggerCharacters)) {
for (const ch of support.signatureHelpRetriggerCharacters) {
this.retriggerChars.add(ch.charCodeAt(0));
}
}
}
Expand All @@ -167,11 +178,16 @@ export class ParameterHintsModel extends Disposable {
}

const lastCharIndex = text.length - 1;
if (this.triggerChars.has(text.charCodeAt(lastCharIndex))) {
const triggerCharCode = text.charCodeAt(lastCharIndex);

if (this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
this.trigger({
triggerReason: modes.SignatureHelpTriggerReason.Retrigger,
triggerCharacter: text.charAt(lastCharIndex)
});
} else if (this.triggerChars.has(triggerCharCode)) {
this.trigger({
triggerReason: this.isTriggered
? modes.SignatureHelpTriggerReason.Retrigger
: modes.SignatureHelpTriggerReason.TriggerCharacter,
triggerReason: modes.SignatureHelpTriggerReason.TriggerCharacter,
triggerCharacter: text.charAt(lastCharIndex)
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ suite('ParameterHintsModel', () => {

disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];

provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.TriggerCharacter);
Expand All @@ -81,6 +82,7 @@ suite('ParameterHintsModel', () => {
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];

provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
++invokeCount;
Expand Down Expand Up @@ -112,6 +114,7 @@ suite('ParameterHintsModel', () => {
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];

provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
++invokeCount;
Expand Down Expand Up @@ -142,6 +145,7 @@ suite('ParameterHintsModel', () => {
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = ['a', 'b', 'c'];
signatureHelpRetriggerCharacters = [];

provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
++invokeCount;
Expand Down Expand Up @@ -170,6 +174,7 @@ suite('ParameterHintsModel', () => {
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = ['a', 'b'];
signatureHelpRetriggerCharacters = [];

provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
++invokeCount;
Expand Down Expand Up @@ -202,6 +207,8 @@ suite('ParameterHintsModel', () => {
let invokeCount = 0;
const longRunningProvider = new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [];
signatureHelpRetriggerCharacters = [];


provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
const count = invokeCount++;
Expand Down Expand Up @@ -243,4 +250,43 @@ suite('ParameterHintsModel', () => {
}
}));
});

test('Provider should be retriggered by retrigger character', (done) => {
const triggerChar = 'a';
const retriggerChar = 'b';

const editor = createMockEditor('');
disposables.push(new ParameterHintsModel(editor, 5));

let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [retriggerChar];

provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
++invokeCount;
if (invokeCount === 1) {
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);

// retrigger after delay for widget to show up
setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: retriggerChar }), 50);
} else if (invokeCount === 2) {
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.Retrigger);
assert.strictEqual(context.triggerCharacter, retriggerChar);
done();
} else {
assert.fail('Unexpected invoke');
}

return emptySigHelpResult;
}
}));

// This should not trigger anything
editor.trigger('keyboard', Handler.Type, { text: retriggerChar });

// But a trigger character should
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
});
});
3 changes: 2 additions & 1 deletion src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4900,7 +4900,8 @@ declare namespace monaco.languages {
* the [parameter hints](https://code.visualstudio.com/docs/editor/intellisense)-feature.
*/
export interface SignatureHelpProvider {
signatureHelpTriggerCharacters: string[];
readonly signatureHelpTriggerCharacters: ReadonlyArray<string>;
readonly signatureHelpRetriggerCharacters: ReadonlyArray<string>;
/**
* Provide help for the signature at the given position and document.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,18 @@ declare module 'vscode' {
provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken, context: SignatureHelpContext): ProviderResult<SignatureHelp>;
}

export interface SignatureHelpProviderMetadata {
readonly triggerCharacters: ReadonlyArray<string>;
readonly retriggerCharacters: ReadonlyArray<string>;
}

namespace languages {
export function registerSignatureHelpProvider(
selector: DocumentSelector,
provider: SignatureHelpProvider,
metadata: SignatureHelpProviderMetadata
): Disposable;
}
//#endregion

//#region Alex - OnEnter enhancement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/parts/search/common/search';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata } from '../node/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IHeapService } from './mainThreadHeapService';
Expand Down Expand Up @@ -301,10 +301,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha

// --- parameter hints

$registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], triggerCharacter: string[]): void {
$registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void {
this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.SignatureHelpProvider>{

signatureHelpTriggerCharacters: triggerCharacter,
signatureHelpTriggerCharacters: metadata.triggerCharacters,
signatureHelpRetriggerCharacters: metadata.retriggerCharacters,

provideSignatureHelp: (model: ITextModel, position: EditorPosition, token: CancellationToken, context: modes.SignatureHelpContext): Thenable<modes.SignatureHelp> => {
return this._proxy.$provideSignatureHelp(handle, model.uri, position, context, token);
Expand Down
7 changes: 5 additions & 2 deletions src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,11 @@ export function createApiFactory(
registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable {
return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters));
},
registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, ...triggerCharacters: string[]): vscode.Disposable {
return extHostLanguageFeatures.registerSignatureHelpProvider(checkSelector(selector), provider, triggerCharacters);
registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable {
if (typeof firstItem === 'object') {
return extHostLanguageFeatures.registerSignatureHelpProvider(checkSelector(selector), provider, firstItem);
}
return extHostLanguageFeatures.registerSignatureHelpProvider(checkSelector(selector), provider, typeof firstItem === 'undefined' ? [] : [firstItem, ...remaining]);
},
registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable {
return extHostLanguageFeatures.registerCompletionItemProvider(checkSelector(selector), provider, triggerCharacters);
Expand Down
7 changes: 6 additions & 1 deletion src/vs/workbench/api/node/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ export interface ISerializedDocumentFilter {
exclusive?: boolean;
}

export interface ISerializedSignatureHelpProviderMetadata {
readonly triggerCharacters: ReadonlyArray<string>;
readonly retriggerCharacters: ReadonlyArray<string>;
}

export interface MainThreadLanguageFeaturesShape extends IDisposable {
$unregister(handle: number): void;
$registerOutlineSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: string): void;
Expand All @@ -279,7 +284,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerNavigateTypeSupport(handle: number): void;
$registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportsResolveInitialValues: boolean): void;
$registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void;
$registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], triggerCharacter: string[]): void;
$registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void;
$registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void;
$registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void;
$registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void;
Expand Down
10 changes: 7 additions & 3 deletions src/vs/workbench/api/node/extHostLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
import { asThenable } from 'vs/base/common/async';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto } from './extHost.protocol';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata } from './extHost.protocol';
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
Expand Down Expand Up @@ -1149,9 +1149,13 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {

// --- parameter hints

registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, triggerCharacters: string[]): vscode.Disposable {
registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars?: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable {
const metadata: ISerializedSignatureHelpProviderMetadata = Array.isArray(metadataOrTriggerChars)
? { triggerCharacters: metadataOrTriggerChars, retriggerCharacters: [] }
: metadataOrTriggerChars;

const handle = this._addNewAdapter(new SignatureHelpAdapter(this._documents, provider));
this._proxy.$registerSignatureHelpProvider(handle, this._transformDocumentSelector(selector), triggerCharacters);
this._proxy.$registerSignatureHelpProvider(handle, this._transformDocumentSelector(selector), metadata);
return this._createDisposable(handle);
}

Expand Down

0 comments on commit 2f51e97

Please sign in to comment.