mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-24 05:40:29 -04:00
Merge from master
This commit is contained in:
@@ -3,17 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./parameterHints';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
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 { createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event, Emitter, chain } from 'vs/base/common/event';
|
||||
import { domEvent, stop } from 'vs/base/browser/event';
|
||||
@@ -31,44 +28,55 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export interface TriggerContext {
|
||||
readonly triggerKind: modes.SignatureHelpTriggerKind;
|
||||
readonly triggerCharacter?: string;
|
||||
}
|
||||
|
||||
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 _onHint = this._register(new Emitter<IHintEvent>());
|
||||
onHint: Event<IHintEvent> = this._onHint.event;
|
||||
private readonly _onHint = this._register(new Emitter<IHintEvent>());
|
||||
public readonly onHint: Event<IHintEvent> = this._onHint.event;
|
||||
|
||||
private _onCancel = this._register(new Emitter<void>());
|
||||
onCancel: Event<void> = this._onCancel.event;
|
||||
private readonly _onCancel = this._register(new Emitter<void>());
|
||||
public readonly onCancel: Event<void> = this._onCancel.event;
|
||||
|
||||
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();
|
||||
private retriggerChars = new CharacterSet();
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
private throttledDelayer: Delayer<boolean>;
|
||||
private provideSignatureHelpRequest?: CancelablePromise<modes.SignatureHelp | null | undefined>;
|
||||
|
||||
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 Delayer(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();
|
||||
@@ -76,6 +84,7 @@ export class ParameterHintsModel extends Disposable {
|
||||
|
||||
cancel(silent: boolean = false): void {
|
||||
this.active = false;
|
||||
this.pending = false;
|
||||
|
||||
this.throttledDelayer.cancel();
|
||||
|
||||
@@ -89,23 +98,39 @@ export class ParameterHintsModel extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
trigger(delay = ParameterHintsModel.DELAY): void {
|
||||
if (!SignatureHelpProviderRegistry.has(this.editor.getModel())) {
|
||||
trigger(context: TriggerContext, delay?: number): void {
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (model === null || !modes.SignatureHelpProviderRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cancel(true);
|
||||
return this.throttledDelayer.schedule(delay);
|
||||
this.throttledDelayer.trigger(
|
||||
() => this.doTrigger({
|
||||
triggerKind: context.triggerKind,
|
||||
triggerCharacter: context.triggerCharacter,
|
||||
isRetrigger: this.isTriggered,
|
||||
}), delay).then(undefined, onUnexpectedError);
|
||||
}
|
||||
|
||||
private doTrigger(): void {
|
||||
if (this.provideSignatureHelpRequest) {
|
||||
this.provideSignatureHelpRequest.cancel();
|
||||
private doTrigger(triggerContext: modes.SignatureHelpContext): Promise<boolean> {
|
||||
this.cancel(true);
|
||||
|
||||
if (!this.editor.hasModel()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.provideSignatureHelpRequest = createCancelablePromise(token => provideSignatureHelp(this.editor.getModel(), this.editor.getPosition(), token));
|
||||
const model = this.editor.getModel();
|
||||
const position = this.editor.getPosition();
|
||||
|
||||
this.pending = true;
|
||||
|
||||
this.provideSignatureHelpRequest = createCancelablePromise(token =>
|
||||
provideSignatureHelp(model!, position!, triggerContext, token));
|
||||
|
||||
return this.provideSignatureHelpRequest.then(result => {
|
||||
this.pending = false;
|
||||
|
||||
this.provideSignatureHelpRequest.then(result => {
|
||||
if (!result || !result.signatures || result.signatures.length === 0) {
|
||||
this.cancel();
|
||||
this._onCancel.fire(void 0);
|
||||
@@ -117,59 +142,79 @@ export class ParameterHintsModel extends Disposable {
|
||||
this._onHint.fire(event);
|
||||
return true;
|
||||
|
||||
}).catch(onUnexpectedError);
|
||||
}).catch(error => {
|
||||
this.pending = false;
|
||||
onUnexpectedError(error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
isTriggered(): boolean {
|
||||
return this.active || this.throttledDelayer.isScheduled();
|
||||
private get isTriggered(): boolean {
|
||||
return this.active || this.pending || this.throttledDelayer.isTriggered();
|
||||
}
|
||||
|
||||
private onModelChanged(): void {
|
||||
this.cancel();
|
||||
|
||||
this.triggerCharactersListeners = dispose(this.triggerCharactersListeners);
|
||||
// Update trigger characters
|
||||
this.triggerChars = new CharacterSet();
|
||||
this.retriggerChars = 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));
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const triggerCharCode = text.charCodeAt(lastCharIndex);
|
||||
|
||||
if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
|
||||
this.trigger({
|
||||
triggerKind: modes.SignatureHelpTriggerKind.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({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
|
||||
}
|
||||
}
|
||||
|
||||
private onModelContentChange(): void {
|
||||
if (this.isTriggered()) {
|
||||
this.trigger();
|
||||
if (this.isTriggered) {
|
||||
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorConfigurationChange(): void {
|
||||
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints;
|
||||
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
|
||||
|
||||
if (!this.enabled) {
|
||||
this.cancel();
|
||||
@@ -190,7 +235,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
private readonly markdownRenderer: MarkdownRenderer;
|
||||
private renderDisposeables: IDisposable[];
|
||||
private model: ParameterHintsModel;
|
||||
private model: ParameterHintsModel | null;
|
||||
private readonly keyVisible: IContextKey<boolean>;
|
||||
private readonly keyMultipleSignatures: IContextKey<boolean>;
|
||||
private element: HTMLElement;
|
||||
@@ -199,8 +244,8 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
private overloads: HTMLElement;
|
||||
private currentSignature: number;
|
||||
private visible: boolean;
|
||||
private hints: SignatureHelp;
|
||||
private announcedLabel: string;
|
||||
private hints: modes.SignatureHelp | null;
|
||||
private announcedLabel: string | null;
|
||||
private scrollbar: DomScrollableElement;
|
||||
private disposables: IDisposable[];
|
||||
|
||||
@@ -262,6 +307,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
this.editor.addContentWidget(this);
|
||||
this.hide();
|
||||
|
||||
this.element.style.userSelect = 'text';
|
||||
this.disposables.push(this.editor.onDidChangeCursorSelection(e => {
|
||||
if (this.visible) {
|
||||
this.editor.layoutContentWidget(this);
|
||||
@@ -294,7 +340,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
this.keyVisible.set(true);
|
||||
this.visible = true;
|
||||
TPromise.timeout(100).done(() => dom.addClass(this.element, 'visible'));
|
||||
setTimeout(() => dom.addClass(this.element, 'visible'), 100);
|
||||
this.editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
@@ -315,7 +361,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
this.editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition {
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
if (this.visible) {
|
||||
return {
|
||||
position: this.editor.getPosition(),
|
||||
@@ -326,6 +372,10 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (!this.hints) {
|
||||
return;
|
||||
}
|
||||
|
||||
const multiple = this.hints.signatures.length > 1;
|
||||
dom.toggleClass(this.element, 'multiple', multiple);
|
||||
this.keyMultipleSignatures.set(multiple);
|
||||
@@ -369,13 +419,14 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
this.renderDisposeables.push(renderedContents);
|
||||
documentation.appendChild(renderedContents.element);
|
||||
}
|
||||
dom.append(this.docs, $('p', null, documentation));
|
||||
dom.append(this.docs, $('p', {}, documentation));
|
||||
}
|
||||
|
||||
dom.toggleClass(this.signature, 'has-docs', !!signature.documentation);
|
||||
|
||||
if (typeof signature.documentation === 'string') {
|
||||
dom.append(this.docs, $('p', null, signature.documentation));
|
||||
if (signature.documentation === undefined) { /** no op */ }
|
||||
else if (typeof signature.documentation === 'string') {
|
||||
dom.append(this.docs, $('p', {}, signature.documentation));
|
||||
} else {
|
||||
const renderedContents = this.markdownRenderer.render(signature.documentation);
|
||||
dom.addClass(renderedContents.element, 'markdown-docs');
|
||||
@@ -392,7 +443,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
this.overloads.textContent = currentOverload;
|
||||
|
||||
if (activeParameter) {
|
||||
const labelToAnnounce = activeParameter.label;
|
||||
const labelToAnnounce = this.getParameterLabel(signature, this.hints.activeParameter);
|
||||
// Select method gets called on every user type while parameter hints are visible.
|
||||
// We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
|
||||
|
||||
@@ -406,40 +457,44 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
private renderParameters(parent: HTMLElement, signature: SignatureInformation, currentParameter: number): void {
|
||||
let end = signature.label.length;
|
||||
let idx = 0;
|
||||
let element: HTMLSpanElement;
|
||||
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
|
||||
|
||||
for (let i = signature.parameters.length - 1; i >= 0; i--) {
|
||||
const parameter = signature.parameters[i];
|
||||
idx = signature.label.lastIndexOf(parameter.label, end - 1);
|
||||
let [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
|
||||
|
||||
let signatureLabelOffset = 0;
|
||||
let signatureLabelEnd = 0;
|
||||
let beforeSpan = document.createElement('span');
|
||||
beforeSpan.textContent = signature.label.substring(0, start);
|
||||
|
||||
if (idx >= 0) {
|
||||
signatureLabelOffset = idx;
|
||||
signatureLabelEnd = idx + parameter.label.length;
|
||||
}
|
||||
let paramSpan = document.createElement('span');
|
||||
paramSpan.textContent = signature.label.substring(start, end);
|
||||
paramSpan.className = 'parameter active';
|
||||
|
||||
// non parameter part
|
||||
element = document.createElement('span');
|
||||
element.textContent = signature.label.substring(signatureLabelEnd, end);
|
||||
dom.prepend(parent, element);
|
||||
let afterSpan = document.createElement('span');
|
||||
afterSpan.textContent = signature.label.substring(end);
|
||||
|
||||
// parameter part
|
||||
element = document.createElement('span');
|
||||
element.className = `parameter ${i === currentParameter ? 'active' : ''}`;
|
||||
element.textContent = signature.label.substring(signatureLabelOffset, signatureLabelEnd);
|
||||
dom.prepend(parent, element);
|
||||
dom.append(parent, beforeSpan, paramSpan, afterSpan);
|
||||
}
|
||||
|
||||
end = signatureLabelOffset;
|
||||
private getParameterLabel(signature: modes.SignatureInformation, paramIdx: number): string {
|
||||
const param = signature.parameters[paramIdx];
|
||||
if (typeof param.label === 'string') {
|
||||
return param.label;
|
||||
} else {
|
||||
return signature.label.substring(param.label[0], param.label[1]);
|
||||
}
|
||||
}
|
||||
|
||||
private getParameterLabelOffsets(signature: modes.SignatureInformation, paramIdx: number): [number, number] {
|
||||
const param = signature.parameters[paramIdx];
|
||||
if (!param) {
|
||||
return [0, 0];
|
||||
} else if (Array.isArray(param.label)) {
|
||||
return param.label;
|
||||
} else {
|
||||
const idx = signature.label.lastIndexOf(param.label);
|
||||
return idx >= 0
|
||||
? [idx, idx + param.label.length]
|
||||
: [0, 0];
|
||||
}
|
||||
// non parameter part
|
||||
element = document.createElement('span');
|
||||
element.textContent = signature.label.substring(0, end);
|
||||
dom.prepend(parent, element);
|
||||
}
|
||||
|
||||
// private select(position: number): void {
|
||||
@@ -474,36 +529,59 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
// }
|
||||
|
||||
next(): boolean {
|
||||
if (!this.hints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const length = this.hints.signatures.length;
|
||||
const last = (this.currentSignature % length) === (length - 1);
|
||||
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
|
||||
|
||||
// If there is only one signature, or we're on last signature of list
|
||||
if (length < 2 || last) {
|
||||
if ((length < 2 || last) && !cycle) {
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentSignature++;
|
||||
if (last && cycle) {
|
||||
this.currentSignature = 0;
|
||||
} else {
|
||||
this.currentSignature++;
|
||||
}
|
||||
|
||||
this.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
previous(): boolean {
|
||||
if (!this.hints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const length = this.hints.signatures.length;
|
||||
const first = this.currentSignature === 0;
|
||||
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
|
||||
|
||||
if (length < 2 || first) {
|
||||
// If there is only one signature, or we're on first signature of list
|
||||
if ((length < 2 || first) && !cycle) {
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentSignature--;
|
||||
if (first && cycle) {
|
||||
this.currentSignature = length - 1;
|
||||
} else {
|
||||
this.currentSignature--;
|
||||
}
|
||||
|
||||
this.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.model.cancel();
|
||||
if (this.model) {
|
||||
this.model.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
@@ -514,8 +592,10 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
return ParameterHintsWidget.ID;
|
||||
}
|
||||
|
||||
trigger(): void {
|
||||
this.model.trigger(0);
|
||||
trigger(context: TriggerContext): void {
|
||||
if (this.model) {
|
||||
this.model.trigger(context, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private updateMaxHeight(): void {
|
||||
@@ -535,7 +615,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
let border = theme.getColor(editorHoverBorder);
|
||||
const border = theme.getColor(editorHoverBorder);
|
||||
if (border) {
|
||||
let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget { border: ${borderWidth}px solid ${border}; }`);
|
||||
@@ -543,7 +623,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid ${border.transparent(0.5)}; }`);
|
||||
|
||||
}
|
||||
let background = theme.getColor(editorHoverBackground);
|
||||
const background = theme.getColor(editorHoverBackground);
|
||||
if (background) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget { background-color: ${background}; }`);
|
||||
}
|
||||
@@ -553,7 +633,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`);
|
||||
}
|
||||
|
||||
let codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
const codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
if (codeBackground) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user