Skip to content

Commit

Permalink
Initial work on signature help context
Browse files Browse the repository at this point in the history
Fixes #54972

Adds `SignatureHelpContext`. This tells providers why signature help was requested

TODO:

- [ ] Better understand semantics of retrigger. Should `retrigger` be an flag instead of a `triggerReason`?
- [ ]  Fix skipped test
- [ ] Add more tests for trigger reasons / trigger characters
  • Loading branch information
mjbvz committed Sep 7, 2018
1 parent 8524c52 commit 4593dc2
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 70 deletions.
Expand Up @@ -20,13 +20,17 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {
public async provideSignatureHelp(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
token: vscode.CancellationToken,
context?: vscode.SignatureHelpContext,
): Promise<vscode.SignatureHelp | undefined> {
const filepath = this.client.toPath(document.uri);
if (!filepath) {
return undefined;
}
const args: Proto.SignatureHelpRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
const args: Proto.SignatureHelpRequestArgs = {
...typeConverters.Position.toFileLocationRequestArgs(filepath, position),
triggerReason: toTsTriggerReason(context!)
};

let info: Proto.SignatureHelpItems;
try {
Expand Down Expand Up @@ -71,6 +75,23 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {
}
}

function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.SignatureHelpTriggerReason {
switch (context.triggerReason) {
case vscode.SignatureHelpTriggerReason.Retrigger:
return { kind: 'retrigger' };

case vscode.SignatureHelpTriggerReason.TriggerCharacter:
if (context.triggerCharacter) {
return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any };
} else {
return { kind: 'invoked' };
}

case vscode.SignatureHelpTriggerReason.Invoke:
default:
return { kind: 'invoked' };
}
}
export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
Expand Down
14 changes: 13 additions & 1 deletion src/vs/editor/common/modes.ts
Expand Up @@ -447,6 +447,18 @@ export interface SignatureHelp {
*/
activeParameter: number;
}

export enum SignatureHelpTriggerReason {
Invoke = 1,
TriggerCharacter = 2,
Retrigger = 3,
}

export interface SignatureHelpContext {
triggerReason: SignatureHelpTriggerReason;
triggerCharacter?: string;
}

/**
* The signature help provider interface defines the contract between extensions and
* the [parameter hints](https://code.visualstudio.com/docs/editor/intellisense)-feature.
Expand All @@ -458,7 +470,7 @@ export interface SignatureHelpProvider {
/**
* Provide help for the signature at the given position and document.
*/
provideSignatureHelp(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<SignatureHelp>;
provideSignatureHelp(model: model.ITextModel, position: Position, token: CancellationToken, context: SignatureHelpContext): ProviderResult<SignatureHelp>;
}

/**
Expand Down
7 changes: 4 additions & 3 deletions src/vs/editor/contrib/parameterHints/parameterHints.ts
Expand Up @@ -16,6 +16,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ParameterHintsWidget } from './parameterHintsWidget';
import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import * as modes from 'vs/editor/common/modes';

class ParameterHintsController implements IEditorContribution {

Expand Down Expand Up @@ -49,8 +50,8 @@ class ParameterHintsController implements IEditorContribution {
this.widget.next();
}

trigger(): void {
this.widget.trigger();
trigger(context: modes.SignatureHelpContext): void {
this.widget.trigger(context);
}

dispose(): void {
Expand All @@ -77,7 +78,7 @@ export class TriggerParameterHintsAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
let controller = ParameterHintsController.get(editor);
if (controller) {
controller.trigger();
controller.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Invoke });
}
}
}
Expand Down
74 changes: 46 additions & 28 deletions src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts
Expand Up @@ -10,7 +10,7 @@ import * as nls from 'vs/nls';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { SignatureHelp, SignatureInformation, SignatureHelpProviderRegistry } from 'vs/editor/common/modes';
import * as modes from 'vs/editor/common/modes';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { RunOnceScheduler, createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
Expand All @@ -31,12 +31,12 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
const $ = dom.$;

export interface IHintEvent {
hints: SignatureHelp;
hints: modes.SignatureHelp;
}

export class ParameterHintsModel extends Disposable {

static DELAY = 120; // ms
private static readonly DEFAULT_DELAY = 120; // ms

private readonly _onHint = this._register(new Emitter<IHintEvent>());
public readonly onHint: Event<IHintEvent> = this._onHint.event;
Expand All @@ -48,17 +48,23 @@ export class ParameterHintsModel extends Disposable {
private enabled: boolean;
private triggerCharactersListeners: IDisposable[];
private active: boolean;
private triggerChars = new CharacterSet();

private triggerContext: modes.SignatureHelpContext | undefined;
private throttledDelayer: RunOnceScheduler;
private provideSignatureHelpRequest?: CancelablePromise<SignatureHelp>;
private provideSignatureHelpRequest?: CancelablePromise<modes.SignatureHelp>;

constructor(editor: ICodeEditor) {
constructor(
editor: ICodeEditor,
delay: number = ParameterHintsModel.DEFAULT_DELAY
) {
super();

this.editor = editor;
this.enabled = false;
this.triggerCharactersListeners = [];

this.throttledDelayer = new RunOnceScheduler(() => this.doTrigger(), ParameterHintsModel.DELAY);
this.throttledDelayer = new RunOnceScheduler(() => this.doTrigger(), delay);

this.active = false;

Expand All @@ -67,14 +73,16 @@ export class ParameterHintsModel extends Disposable {
this._register(this.editor.onDidChangeModelLanguage(_ => this.onModelChanged()));
this._register(this.editor.onDidChangeCursorSelection(e => this.onCursorChange(e)));
this._register(this.editor.onDidChangeModelContent(e => this.onModelContentChange()));
this._register(SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
this._register(modes.SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
this._register(this.editor.onDidType(text => this.onDidType(text)));

this.onEditorConfigurationChange();
this.onModelChanged();
}

cancel(silent: boolean = false): void {
this.active = false;
this.triggerContext = undefined;

this.throttledDelayer.cancel();

Expand All @@ -88,12 +96,13 @@ export class ParameterHintsModel extends Disposable {
}
}

trigger(delay = ParameterHintsModel.DELAY): void {
if (!SignatureHelpProviderRegistry.has(this.editor.getModel())) {
trigger(context: modes.SignatureHelpContext, delay?: number): void {
if (!modes.SignatureHelpProviderRegistry.has(this.editor.getModel())) {
return;
}

this.cancel(true);
this.triggerContext = context;
return this.throttledDelayer.schedule(delay);
}

Expand All @@ -102,7 +111,10 @@ export class ParameterHintsModel extends Disposable {
this.provideSignatureHelpRequest.cancel();
}

this.provideSignatureHelpRequest = createCancelablePromise(token => provideSignatureHelp(this.editor.getModel(), this.editor.getPosition(), token));
this.provideSignatureHelpRequest = createCancelablePromise(token =>
provideSignatureHelp(this.editor.getModel(), this.editor.getPosition(), this.triggerContext || { triggerReason: modes.SignatureHelpTriggerReason.Invoke }, token));

this.triggerContext = undefined;

this.provideSignatureHelpRequest.then(result => {
if (!result || !result.signatures || result.signatures.length === 0) {
Expand All @@ -126,44 +138,50 @@ export class ParameterHintsModel extends Disposable {
private onModelChanged(): void {
this.cancel();

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

const model = this.editor.getModel();
if (!model) {
return;
}

const triggerChars = new CharacterSet();
for (const support of SignatureHelpProviderRegistry.ordered(model)) {
for (const support of modes.SignatureHelpProviderRegistry.ordered(model)) {
if (Array.isArray(support.signatureHelpTriggerCharacters)) {
for (const ch of support.signatureHelpTriggerCharacters) {
triggerChars.add(ch.charCodeAt(0));
this.triggerChars.add(ch.charCodeAt(0));
}
}
}
}

this.triggerCharactersListeners.push(this.editor.onDidType((text: string) => {
if (!this.enabled) {
return;
}
private onDidType(text: string) {
if (!this.enabled) {
return;
}

if (triggerChars.has(text.charCodeAt(text.length - 1))) {
this.trigger();
}
}));
const lastCharIndex = text.length - 1;
if (this.triggerChars.has(text.charCodeAt(lastCharIndex))) {
this.trigger({
triggerReason: this.active
? modes.SignatureHelpTriggerReason.Retrigger
: modes.SignatureHelpTriggerReason.TriggerCharacter,
triggerCharacter: text.charAt(lastCharIndex)
});
}
}

private onCursorChange(e: ICursorSelectionChangedEvent): void {
if (e.source === 'mouse') {
this.cancel();
} else if (this.isTriggered()) {
this.trigger();
this.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Retrigger });
}
}

private onModelContentChange(): void {
if (this.isTriggered()) {
this.trigger();
this.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Retrigger });
}
}

Expand Down Expand Up @@ -198,7 +216,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
private overloads: HTMLElement;
private currentSignature: number;
private visible: boolean;
private hints: SignatureHelp;
private hints: modes.SignatureHelp;
private announcedLabel: string;
private scrollbar: DomScrollableElement;
private disposables: IDisposable[];
Expand Down Expand Up @@ -405,7 +423,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
this.scrollbar.scanDomNode();
}

private renderParameters(parent: HTMLElement, signature: SignatureInformation, currentParameter: number): void {
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
let end = signature.label.length;
let idx = 0;
let element: HTMLSpanElement;
Expand Down Expand Up @@ -526,8 +544,8 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
return ParameterHintsWidget.ID;
}

trigger(): void {
this.model.trigger(0);
trigger(context: modes.SignatureHelpContext): void {
this.model.trigger(context, 0);
}

private updateMaxHeight(): void {
Expand Down
11 changes: 6 additions & 5 deletions src/vs/editor/contrib/parameterHints/provideSignatureHelp.ts
Expand Up @@ -8,7 +8,7 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model';
import { SignatureHelp, SignatureHelpProviderRegistry } from 'vs/editor/common/modes';
import * as modes from 'vs/editor/common/modes';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { CancellationToken } from 'vs/base/common/cancellation';

Expand All @@ -17,13 +17,14 @@ export const Context = {
MultipleSignatures: new RawContextKey<boolean>('parameterHintsMultipleSignatures', false),
};

export function provideSignatureHelp(model: ITextModel, position: Position, token: CancellationToken): Promise<SignatureHelp> {
export function provideSignatureHelp(model: ITextModel, position: Position, context: modes.SignatureHelpContext, token: CancellationToken): Promise<modes.SignatureHelp> {

const supports = SignatureHelpProviderRegistry.ordered(model);
const supports = modes.SignatureHelpProviderRegistry.ordered(model);

return first2(supports.map(support => () => {
return Promise.resolve(support.provideSignatureHelp(model, position, token)).catch(onUnexpectedExternalError);
return Promise.resolve(support.provideSignatureHelp(model, position, token, context)).catch(onUnexpectedExternalError);
}));
}

registerDefaultLanguageCommand('_executeSignatureHelpProvider', (model, position) => provideSignatureHelp(model, position, CancellationToken.None));
registerDefaultLanguageCommand('_executeSignatureHelpProvider', (model, position) =>
provideSignatureHelp(model, position, { triggerReason: modes.SignatureHelpTriggerReason.Invoke }, CancellationToken.None));

0 comments on commit 4593dc2

Please sign in to comment.