Initial work on signature help context (#58135)

* Initial work on signature help context

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

* Fix unit test

* Make sure we retrigger sig help if it is already showing

* Add test for dismiss and re-invoke

* Extract some constants

* Extract createMockEditor
This commit is contained in:
Matt Bierner
2018-09-10 17:44:21 -07:00
committed by GitHub
parent 4f2ffa00f6
commit d33b1c3063
15 changed files with 373 additions and 92 deletions
@@ -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 {
@@ -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,
+13 -1
View File
@@ -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.
@@ -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>;
}
/**
@@ -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 {
@@ -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 {
@@ -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 });
}
}
}
@@ -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';
@@ -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;
@@ -47,27 +47,33 @@ export class ParameterHintsModel extends Disposable {
private editor: ICodeEditor;
private enabled: boolean;
private triggerCharactersListeners: IDisposable[];
private active: boolean;
private throttledDelayer: RunOnceScheduler;
private provideSignatureHelpRequest?: CancelablePromise<SignatureHelp>;
private active: boolean = false;
private pending: boolean = false;
private triggerChars = new CharacterSet();
constructor(editor: ICodeEditor) {
private triggerContext: modes.SignatureHelpContext | undefined;
private throttledDelayer: RunOnceScheduler;
private provideSignatureHelpRequest?: CancelablePromise<modes.SignatureHelp>;
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.active = false;
this.throttledDelayer = new RunOnceScheduler(() => this.doTrigger(), delay);
this._register(this.editor.onDidChangeConfiguration(() => this.onEditorConfigurationChange()));
this._register(this.editor.onDidChangeModel(e => this.onModelChanged()));
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();
@@ -75,6 +81,8 @@ export class ParameterHintsModel extends Disposable {
cancel(silent: boolean = false): void {
this.active = false;
this.pending = false;
this.triggerContext = undefined;
this.throttledDelayer.cancel();
@@ -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);
}
@@ -102,9 +111,17 @@ export class ParameterHintsModel extends Disposable {
this.provideSignatureHelpRequest.cancel();
}
this.provideSignatureHelpRequest = createCancelablePromise(token => provideSignatureHelp(this.editor.getModel(), this.editor.getPosition(), token));
this.pending = true;
const triggerContext = this.triggerContext || { triggerReason: modes.SignatureHelpTriggerReason.Invoke };
this.triggerContext = undefined;
this.provideSignatureHelpRequest = createCancelablePromise(token =>
provideSignatureHelp(this.editor.getModel(), this.editor.getPosition(), triggerContext, token));
this.provideSignatureHelpRequest.then(result => {
this.pending = false;
if (!result || !result.signatures || result.signatures.length === 0) {
this.cancel();
this._onCancel.fire(void 0);
@@ -116,54 +133,63 @@ export class ParameterHintsModel extends Disposable {
this._onHint.fire(event);
return true;
}).catch(onUnexpectedError);
}).catch(error => {
this.pending = false;
onUnexpectedError(error);
});
}
isTriggered(): boolean {
return this.active || this.throttledDelayer.isScheduled();
private get isTriggered(): boolean {
return this.active || this.pending || this.throttledDelayer.isScheduled();
}
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.isTriggered
? 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();
} else if (this.isTriggered) {
this.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Retrigger });
}
}
private onModelContentChange(): void {
if (this.isTriggered()) {
this.trigger();
if (this.isTriggered) {
this.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Retrigger });
}
}
@@ -198,7 +224,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[];
@@ -405,7 +431,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;
@@ -526,8 +552,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 {
@@ -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';
@@ -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));
@@ -5,60 +5,214 @@
import * as assert from 'assert';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
import { Handler } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { SignatureHelp, SignatureHelpProvider, SignatureHelpProviderRegistry } from 'vs/editor/common/modes';
import { TestCodeEditor, createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import * as modes from 'vs/editor/common/modes';
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IStorageService, NullStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ParameterHintsModel } from '../parameterHintsWidget';
function createMockEditor(model: TextModel): TestCodeEditor {
return createTestCodeEditor({
model: model,
serviceCollection: new ServiceCollection(
[ITelemetryService, NullTelemetryService],
[IStorageService, NullStorageService]
)
});
}
const mockFile = URI.parse('test:somefile.ttt');
const mockFileSelector = { scheme: 'test' };
const emptySigHelpResult = {
signatures: [{
label: 'none',
parameters: []
}],
activeParameter: 0,
activeSignature: 0
};
suite('ParameterHintsModel', () => {
let disposables: IDisposable[] = [];
setup(function () {
disposables = dispose(disposables);
});
test('Should cancel existing request when new request comes in', () => {
const textModel = TextModel.createFromString('abc def', undefined, undefined, URI.parse('test:somefile.ttt'));
function createMockEditor(fileContents: string) {
const textModel = TextModel.createFromString(fileContents, undefined, undefined, mockFile);
const editor = createTestCodeEditor({
model: textModel,
serviceCollection: new ServiceCollection(
[ITelemetryService, NullTelemetryService],
[IStorageService, NullStorageService]
)
});
disposables.push(textModel);
disposables.push(editor);
return editor;
}
const editor = createMockEditor(textModel);
test('Provider should get trigger character on type', (done) => {
const triggerChar = '(';
const editor = createMockEditor('');
disposables.push(new ParameterHintsModel(editor));
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);
done();
return undefined;
}
}));
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
});
test('Provider should be retriggered if already active', (done) => {
const triggerChar = '(';
const editor = createMockEditor('');
disposables.push(new ParameterHintsModel(editor));
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
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
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
} else {
assert.strictEqual(invokeCount, 2);
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.Retrigger);
assert.strictEqual(context.triggerCharacter, triggerChar);
done();
}
return emptySigHelpResult;
}
}));
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
});
test('Provider should not be retriggered if previous help is canceled first', (done) => {
const triggerChar = '(';
const editor = createMockEditor('');
const hintModel = new ParameterHintsModel(editor);
disposables.push(hintModel);
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
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);
// Cancel and retrigger
hintModel.cancel();
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
} else {
assert.strictEqual(invokeCount, 2);
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);
done();
}
return emptySigHelpResult;
}
}));
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
});
test('Provider should get last trigger character when triggered multiple times and only be invoked once', (done) => {
const editor = createMockEditor('');
disposables.push(new ParameterHintsModel(editor, 5));
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = ['a', 'b', 'c'];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
++invokeCount;
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.Retrigger);
assert.strictEqual(context.triggerCharacter, 'c');
// Give some time to allow for later triggers
setTimeout(() => {
assert.strictEqual(invokeCount, 1);
done();
}, 50);
return undefined;
}
}));
editor.trigger('keyboard', Handler.Type, { text: 'a' });
editor.trigger('keyboard', Handler.Type, { text: 'b' });
editor.trigger('keyboard', Handler.Type, { text: 'c' });
});
test('Provider should be retriggered if already active', (done) => {
const editor = createMockEditor('');
disposables.push(new ParameterHintsModel(editor, 5));
let invokeCount = 0;
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = ['a', 'b'];
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, 'a');
// retrigger after delay for widget to show up
setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: 'b' }), 50);
} else if (invokeCount === 2) {
assert.strictEqual(context.triggerReason, modes.SignatureHelpTriggerReason.Retrigger);
assert.strictEqual(context.triggerCharacter, 'b');
done();
} else {
assert.fail('Unexpected invoke');
}
return emptySigHelpResult;
}
}));
editor.trigger('keyboard', Handler.Type, { text: 'a' });
});
test('Should cancel existing request when new request comes in', () => {
const editor = createMockEditor('abc def');
const hintsModel = new ParameterHintsModel(editor);
let didRequestCancellationOf = -1;
let invokeCount = 0;
const longRunningProvider = new class implements SignatureHelpProvider {
signatureHelpTriggerCharacters: string[] = [];
const longRunningProvider = new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [];
provideSignatureHelp(model: ITextModel, position: Position, token: CancellationToken): SignatureHelp | Thenable<SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
const count = invokeCount++;
token.onCancellationRequested(() => { didRequestCancellationOf = count; });
// retrigger on first request
if (count === 0) {
hintsModel.trigger(0);
hintsModel.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Invoke }, 0);
}
return new Promise<SignatureHelp>(resolve => {
return new Promise<modes.SignatureHelp>(resolve => {
setTimeout(() => {
resolve({
signatures: [{
@@ -73,9 +227,9 @@ suite('ParameterHintsModel', () => {
}
};
disposables.push(SignatureHelpProviderRegistry.register({ scheme: 'test' }, longRunningProvider));
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, longRunningProvider));
hintsModel.trigger(0);
hintsModel.trigger({ triggerReason: modes.SignatureHelpTriggerReason.Invoke }, 0);
assert.strictEqual(-1, didRequestCancellationOf);
return new Promise((resolve, reject) =>
@@ -880,6 +880,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages {
SymbolKind: modes.SymbolKind,
IndentAction: IndentAction,
SuggestTriggerKind: modes.SuggestTriggerKind,
FoldingRangeKind: modes.FoldingRangeKind
FoldingRangeKind: modes.FoldingRangeKind,
SignatureHelpTriggerReason: modes.SignatureHelpTriggerReason,
};
}
+12 -1
View File
@@ -4884,6 +4884,17 @@ declare namespace monaco.languages {
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.
@@ -4893,7 +4904,7 @@ declare namespace monaco.languages {
/**
* Provide help for the signature at the given position and document.
*/
provideSignatureHelp(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<SignatureHelp>;
provideSignatureHelp(model: editor.ITextModel, position: Position, token: CancellationToken, context: SignatureHelpContext): ProviderResult<SignatureHelp>;
}
/**
+48
View File
@@ -808,4 +808,52 @@ declare module 'vscode' {
export const onDidRenameFile: Event<FileRenameEvent>;
}
//#endregion
//#region Signature Help
/**
* How a [Signature provider](#SignatureHelpProvider) was triggered
*/
export enum SignatureHelpTriggerReason {
/**
* Signature help was invoked manually by the user or by a command.
*/
Invoke = 1,
/**
* Signature help was triggered by a trigger character.
*/
TriggerCharacter = 2,
/**
* Signature help was retriggered.
*
* Retriggers occur when the signature help is already active and can be caused by typing a trigger character
* or by a cursor move.
*/
Retrigger = 3,
}
/**
* Contains additional information about the context in which a
* [signature help provider](#SignatureHelpProvider.provideSignatureHelp) is triggered.
*/
export interface SignatureHelpContext {
/**
* Action that caused signature help to be requested.
*/
readonly triggerReason: SignatureHelpTriggerReason;
/**
* Character that caused signature help to be requested.
*
* This is `undefined` for manual triggers or retriggers for a cursor move.
*/
readonly triggerCharacter?: string;
}
export interface SignatureHelpProvider {
provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken, context: SignatureHelpContext): ProviderResult<SignatureHelp>;
}
//#endregion
}
@@ -307,10 +307,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
signatureHelpTriggerCharacters: triggerCharacter,
provideSignatureHelp: (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable<modes.SignatureHelp> => {
return this._proxy.$provideSignatureHelp(handle, model.uri, position, token);
provideSignatureHelp: (model: ITextModel, position: EditorPosition, token: CancellationToken, context: modes.SignatureHelpContext): Thenable<modes.SignatureHelp> => {
return this._proxy.$provideSignatureHelp(handle, model.uri, position, context, token);
}
});
}
@@ -759,6 +759,7 @@ export function createApiFactory(
Selection: extHostTypes.Selection,
ShellExecution: extHostTypes.ShellExecution,
ShellQuoting: extHostTypes.ShellQuoting,
SignatureHelpTriggerReason: extHostTypes.SignatureHelpTriggerReason,
SignatureHelp: extHostTypes.SignatureHelp,
SignatureInformation: extHostTypes.SignatureInformation,
SnippetString: extHostTypes.SnippetString,
@@ -843,7 +843,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.SuggestContext, token: CancellationToken): Thenable<SuggestResultDto>;
$resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable<modes.ISuggestion>;
$releaseCompletionItems(handle: number, id: number): void;
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Thenable<modes.SignatureHelp>;
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Thenable<modes.SignatureHelp>;
$provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Thenable<modes.ILink[]>;
$resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Thenable<modes.ILink>;
$provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Thenable<IRawColorInfo[]>;
@@ -714,12 +714,12 @@ class SignatureHelpAdapter {
private readonly _provider: vscode.SignatureHelpProvider
) { }
provideSignatureHelp(resource: URI, position: IPosition, token: CancellationToken): Thenable<modes.SignatureHelp> {
provideSignatureHelp(resource: URI, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Thenable<modes.SignatureHelp> {
const doc = this._documents.getDocumentData(resource).document;
const pos = typeConvert.Position.to(position);
return asThenable(() => this._provider.provideSignatureHelp(doc, pos, token)).then(value => {
return asThenable(() => this._provider.provideSignatureHelp(doc, pos, token, context)).then(value => {
if (value) {
return typeConvert.SignatureHelp.from(value);
}
@@ -1144,8 +1144,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Thenable<modes.SignatureHelp> {
return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position, token));
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Thenable<modes.SignatureHelp> {
return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position, context, token));
}
// --- links
@@ -1064,6 +1064,12 @@ export class SignatureHelp {
}
}
export enum SignatureHelpTriggerReason {
Invoke = 1,
TriggerCharacter = 2,
Retrigger = 3,
}
export enum CompletionTriggerKind {
Invoke = 0,
TriggerCharacter = 1,
@@ -24,7 +24,7 @@ import { IHeapService } from 'vs/workbench/api/electron-browser/mainThreadHeapSe
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen';
import { DocumentSymbolProviderRegistry, DocumentHighlightKind, Hover, ResourceTextEdit } from 'vs/editor/common/modes';
import * as modes from 'vs/editor/common/modes';
import { getCodeLensData } from 'vs/editor/contrib/codelens/codelens';
import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition } from 'vs/editor/contrib/goToDefinition/goToDefinition';
import { getHover } from 'vs/editor/contrib/hover/getHover';
@@ -133,7 +133,7 @@ suite('ExtHostLanguageFeatures', function () {
// --- outline
test('DocumentSymbols, register/deregister', function () {
assert.equal(DocumentSymbolProviderRegistry.all(model).length, 0);
assert.equal(modes.DocumentSymbolProviderRegistry.all(model).length, 0);
let d1 = extHost.registerDocumentSymbolProvider(defaultSelector, <vscode.DocumentSymbolProvider>{
provideDocumentSymbols() {
return <vscode.SymbolInformation[]>[];
@@ -141,7 +141,7 @@ suite('ExtHostLanguageFeatures', function () {
});
return rpcProtocol.sync().then(() => {
assert.equal(DocumentSymbolProviderRegistry.all(model).length, 1);
assert.equal(modes.DocumentSymbolProviderRegistry.all(model).length, 1);
d1.dispose();
return rpcProtocol.sync();
});
@@ -443,7 +443,7 @@ suite('ExtHostLanguageFeatures', function () {
return rpcProtocol.sync().then(() => {
return getHover(model, new EditorPosition(1, 1), CancellationToken.None).then(value => {
assert.equal(value.length, 2);
let [first, second] = value as Hover[];
let [first, second] = value as modes.Hover[];
assert.equal(first.contents[0].value, 'registered second');
assert.equal(second.contents[0].value, 'registered first');
});
@@ -489,7 +489,7 @@ suite('ExtHostLanguageFeatures', function () {
assert.equal(value.length, 1);
let [entry] = value;
assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 });
assert.equal(entry.kind, DocumentHighlightKind.Text);
assert.equal(entry.kind, modes.DocumentHighlightKind.Text);
});
});
});
@@ -513,7 +513,7 @@ suite('ExtHostLanguageFeatures', function () {
assert.equal(value.length, 1);
let [entry] = value;
assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 });
assert.equal(entry.kind, DocumentHighlightKind.Text);
assert.equal(entry.kind, modes.DocumentHighlightKind.Text);
});
});
});
@@ -537,7 +537,7 @@ suite('ExtHostLanguageFeatures', function () {
assert.equal(value.length, 1);
let [entry] = value;
assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 3 });
assert.equal(entry.kind, DocumentHighlightKind.Text);
assert.equal(entry.kind, modes.DocumentHighlightKind.Text);
});
});
});
@@ -838,8 +838,8 @@ suite('ExtHostLanguageFeatures', function () {
return rename(model, new EditorPosition(1, 1), 'newName').then(value => {
// least relevant rename provider
assert.equal(value.edits.length, 2);
assert.equal((<ResourceTextEdit>value.edits[0]).edits.length, 1);
assert.equal((<ResourceTextEdit>value.edits[1]).edits.length, 1);
assert.equal((<modes.ResourceTextEdit>value.edits[0]).edits.length, 1);
assert.equal((<modes.ResourceTextEdit>value.edits[1]).edits.length, 1);
});
});
});
@@ -866,7 +866,7 @@ suite('ExtHostLanguageFeatures', function () {
return rpcProtocol.sync().then(() => {
return provideSignatureHelp(model, new EditorPosition(1, 1), CancellationToken.None).then(value => {
return provideSignatureHelp(model, new EditorPosition(1, 1), { triggerReason: modes.SignatureHelpTriggerReason.Invoke }, CancellationToken.None).then(value => {
assert.ok(value);
});
});
@@ -881,7 +881,7 @@ suite('ExtHostLanguageFeatures', function () {
return rpcProtocol.sync().then(() => {
return provideSignatureHelp(model, new EditorPosition(1, 1), CancellationToken.None).then(value => {
return provideSignatureHelp(model, new EditorPosition(1, 1), { triggerReason: modes.SignatureHelpTriggerReason.Invoke }, CancellationToken.None).then(value => {
assert.equal(value, undefined);
});
});