mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-24 05:40:29 -04:00
Initial VS Code 1.19 source merge (#571)
* Initial 1.19 xcopy * Fix yarn build * Fix numerous build breaks * Next batch of build break fixes * More build break fixes * Runtime breaks * Additional post merge fixes * Fix windows setup file * Fix test failures. * Update license header blocks to refer to source eula
This commit is contained in:
528
src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts
Normal file
528
src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts
Normal file
@@ -0,0 +1,528 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./parameterHints';
|
||||
import nls = require('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 aria = require('vs/base/browser/ui/aria/aria');
|
||||
import { SignatureHelp, SignatureInformation, SignatureHelpProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { RunOnceScheduler } 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';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Context, provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
|
||||
import { editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export interface IHintEvent {
|
||||
hints: SignatureHelp;
|
||||
}
|
||||
|
||||
export class ParameterHintsModel extends Disposable {
|
||||
|
||||
static DELAY = 120; // ms
|
||||
|
||||
private _onHint = this._register(new Emitter<IHintEvent>());
|
||||
onHint: Event<IHintEvent> = this._onHint.event;
|
||||
|
||||
private _onCancel = this._register(new Emitter<void>());
|
||||
onCancel: Event<void> = this._onCancel.event;
|
||||
|
||||
private editor: ICodeEditor;
|
||||
private enabled: boolean;
|
||||
private triggerCharactersListeners: IDisposable[];
|
||||
private active: boolean;
|
||||
private throttledDelayer: RunOnceScheduler;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
super();
|
||||
|
||||
this.editor = editor;
|
||||
this.enabled = false;
|
||||
this.triggerCharactersListeners = [];
|
||||
|
||||
this.throttledDelayer = new RunOnceScheduler(() => this.doTrigger(), ParameterHintsModel.DELAY);
|
||||
|
||||
this.active = false;
|
||||
|
||||
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(SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
|
||||
|
||||
this.onEditorConfigurationChange();
|
||||
this.onModelChanged();
|
||||
}
|
||||
|
||||
cancel(silent: boolean = false): void {
|
||||
this.active = false;
|
||||
|
||||
this.throttledDelayer.cancel();
|
||||
|
||||
if (!silent) {
|
||||
this._onCancel.fire(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
trigger(delay = ParameterHintsModel.DELAY): void {
|
||||
if (!SignatureHelpProviderRegistry.has(this.editor.getModel())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cancel(true);
|
||||
return this.throttledDelayer.schedule(delay);
|
||||
}
|
||||
|
||||
private doTrigger(): void {
|
||||
provideSignatureHelp(this.editor.getModel(), this.editor.getPosition())
|
||||
.then(null, onUnexpectedError)
|
||||
.then(result => {
|
||||
if (!result || !result.signatures || result.signatures.length === 0) {
|
||||
this.cancel();
|
||||
this._onCancel.fire(void 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.active = true;
|
||||
|
||||
const event: IHintEvent = { hints: result };
|
||||
this._onHint.fire(event);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
isTriggered(): boolean {
|
||||
return this.active || this.throttledDelayer.isScheduled();
|
||||
}
|
||||
|
||||
private onModelChanged(): void {
|
||||
if (this.active) {
|
||||
this.cancel();
|
||||
}
|
||||
this.triggerCharactersListeners = dispose(this.triggerCharactersListeners);
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const triggerChars = new CharacterSet();
|
||||
for (const support of SignatureHelpProviderRegistry.ordered(model)) {
|
||||
if (Array.isArray(support.signatureHelpTriggerCharacters)) {
|
||||
for (const ch of support.signatureHelpTriggerCharacters) {
|
||||
triggerChars.add(ch.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.triggerCharactersListeners.push(this.editor.onDidType((text: string) => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (triggerChars.has(text.charCodeAt(text.length - 1))) {
|
||||
this.trigger();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private onCursorChange(e: ICursorSelectionChangedEvent): void {
|
||||
if (e.source === 'mouse') {
|
||||
this.cancel();
|
||||
} else if (this.isTriggered()) {
|
||||
this.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorConfigurationChange(): void {
|
||||
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints;
|
||||
|
||||
if (!this.enabled) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel(true);
|
||||
this.triggerCharactersListeners = dispose(this.triggerCharactersListeners);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
private static readonly ID = 'editor.widget.parameterHintsWidget';
|
||||
|
||||
private markdownRenderer: MarkdownRenderer;
|
||||
private model: ParameterHintsModel;
|
||||
private keyVisible: IContextKey<boolean>;
|
||||
private keyMultipleSignatures: IContextKey<boolean>;
|
||||
private element: HTMLElement;
|
||||
private signature: HTMLElement;
|
||||
private docs: HTMLElement;
|
||||
private overloads: HTMLElement;
|
||||
private currentSignature: number;
|
||||
private visible: boolean;
|
||||
private hints: SignatureHelp;
|
||||
private announcedLabel: string;
|
||||
private scrollbar: DomScrollableElement;
|
||||
private disposables: IDisposable[];
|
||||
|
||||
// Editor.IContentWidget.allowEditorOverflow
|
||||
allowEditorOverflow = true;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IModeService modeService: IModeService,
|
||||
) {
|
||||
this.markdownRenderer = new MarkdownRenderer(editor, modeService, openerService);
|
||||
this.model = new ParameterHintsModel(editor);
|
||||
this.keyVisible = Context.Visible.bindTo(contextKeyService);
|
||||
this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService);
|
||||
this.visible = false;
|
||||
this.disposables = [];
|
||||
|
||||
this.disposables.push(this.model.onHint(e => {
|
||||
this.show();
|
||||
this.hints = e.hints;
|
||||
this.currentSignature = e.hints.activeSignature;
|
||||
this.render();
|
||||
}));
|
||||
|
||||
this.disposables.push(this.model.onCancel(() => {
|
||||
this.hide();
|
||||
}));
|
||||
}
|
||||
|
||||
private createParamaterHintDOMNodes() {
|
||||
this.element = $('.editor-widget.parameter-hints-widget');
|
||||
const wrapper = dom.append(this.element, $('.wrapper'));
|
||||
|
||||
const buttons = dom.append(wrapper, $('.buttons'));
|
||||
const previous = dom.append(buttons, $('.button.previous'));
|
||||
const next = dom.append(buttons, $('.button.next'));
|
||||
|
||||
const onPreviousClick = stop(domEvent(previous, 'click'));
|
||||
onPreviousClick(this.previous, this, this.disposables);
|
||||
|
||||
const onNextClick = stop(domEvent(next, 'click'));
|
||||
onNextClick(this.next, this, this.disposables);
|
||||
|
||||
this.overloads = dom.append(wrapper, $('.overloads'));
|
||||
|
||||
const body = $('.body');
|
||||
this.scrollbar = new DomScrollableElement(body, {});
|
||||
this.disposables.push(this.scrollbar);
|
||||
wrapper.appendChild(this.scrollbar.getDomNode());
|
||||
|
||||
this.signature = dom.append(body, $('.signature'));
|
||||
|
||||
this.docs = dom.append(body, $('.docs'));
|
||||
|
||||
this.currentSignature = 0;
|
||||
|
||||
this.editor.addContentWidget(this);
|
||||
this.hide();
|
||||
|
||||
this.disposables.push(this.editor.onDidChangeCursorSelection(e => {
|
||||
if (this.visible) {
|
||||
this.editor.layoutContentWidget(this);
|
||||
}
|
||||
}));
|
||||
|
||||
const updateFont = () => {
|
||||
const fontInfo = this.editor.getConfiguration().fontInfo;
|
||||
this.element.style.fontSize = `${fontInfo.fontSize}px`;
|
||||
};
|
||||
|
||||
updateFont();
|
||||
|
||||
chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
.filter(e => e.fontInfo)
|
||||
.on(updateFont, null, this.disposables);
|
||||
|
||||
this.disposables.push(this.editor.onDidLayoutChange(e => this.updateMaxHeight()));
|
||||
this.updateMaxHeight();
|
||||
}
|
||||
|
||||
private show(): void {
|
||||
if (!this.model || this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.element) {
|
||||
this.createParamaterHintDOMNodes();
|
||||
}
|
||||
|
||||
this.keyVisible.set(true);
|
||||
this.visible = true;
|
||||
TPromise.timeout(100).done(() => dom.addClass(this.element, 'visible'));
|
||||
this.editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
private hide(): void {
|
||||
if (!this.model || !this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.element) {
|
||||
this.createParamaterHintDOMNodes();
|
||||
}
|
||||
|
||||
this.keyVisible.reset();
|
||||
this.visible = false;
|
||||
this.hints = null;
|
||||
this.announcedLabel = null;
|
||||
dom.removeClass(this.element, 'visible');
|
||||
this.editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition {
|
||||
if (this.visible) {
|
||||
return {
|
||||
position: this.editor.getPosition(),
|
||||
preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
const multiple = this.hints.signatures.length > 1;
|
||||
dom.toggleClass(this.element, 'multiple', multiple);
|
||||
this.keyMultipleSignatures.set(multiple);
|
||||
|
||||
this.signature.innerHTML = '';
|
||||
this.docs.innerHTML = '';
|
||||
|
||||
const signature = this.hints.signatures[this.currentSignature];
|
||||
|
||||
if (!signature) {
|
||||
return;
|
||||
}
|
||||
|
||||
const code = dom.append(this.signature, $('.code'));
|
||||
const hasParameters = signature.parameters.length > 0;
|
||||
|
||||
const fontInfo = this.editor.getConfiguration().fontInfo;
|
||||
code.style.fontSize = `${fontInfo.fontSize}px`;
|
||||
code.style.fontFamily = fontInfo.fontFamily;
|
||||
|
||||
if (!hasParameters) {
|
||||
const label = dom.append(code, $('span'));
|
||||
label.textContent = signature.label;
|
||||
|
||||
} else {
|
||||
this.renderParameters(code, signature, this.hints.activeParameter);
|
||||
}
|
||||
|
||||
const activeParameter = signature.parameters[this.hints.activeParameter];
|
||||
|
||||
if (activeParameter && activeParameter.documentation) {
|
||||
const documentation = $('span.documentation');
|
||||
if (typeof activeParameter.documentation === 'string') {
|
||||
dom.removeClass(this.docs, 'markdown-docs');
|
||||
documentation.textContent = activeParameter.documentation;
|
||||
} else {
|
||||
dom.addClass(this.docs, 'markdown-docs');
|
||||
documentation.appendChild(this.markdownRenderer.render(activeParameter.documentation));
|
||||
}
|
||||
dom.append(this.docs, $('p', null, documentation));
|
||||
}
|
||||
|
||||
dom.toggleClass(this.signature, 'has-docs', !!signature.documentation);
|
||||
|
||||
if (typeof signature.documentation === 'string') {
|
||||
dom.append(this.docs, $('p', null, signature.documentation));
|
||||
} else {
|
||||
dom.append(this.docs, this.markdownRenderer.render(signature.documentation));
|
||||
}
|
||||
|
||||
let currentOverload = String(this.currentSignature + 1);
|
||||
|
||||
if (this.hints.signatures.length < 10) {
|
||||
currentOverload += `/${this.hints.signatures.length}`;
|
||||
}
|
||||
|
||||
this.overloads.textContent = currentOverload;
|
||||
|
||||
if (activeParameter) {
|
||||
const labelToAnnounce = activeParameter.label;
|
||||
// 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.
|
||||
|
||||
if (this.announcedLabel !== labelToAnnounce) {
|
||||
aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
|
||||
this.announcedLabel = labelToAnnounce;
|
||||
}
|
||||
}
|
||||
|
||||
this.editor.layoutContentWidget(this);
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
private renderParameters(parent: HTMLElement, signature: SignatureInformation, currentParameter: number): void {
|
||||
let end = signature.label.length;
|
||||
let idx = 0;
|
||||
let element: HTMLSpanElement;
|
||||
|
||||
for (let i = signature.parameters.length - 1; i >= 0; i--) {
|
||||
const parameter = signature.parameters[i];
|
||||
idx = signature.label.lastIndexOf(parameter.label, end - 1);
|
||||
|
||||
let signatureLabelOffset = 0;
|
||||
let signatureLabelEnd = 0;
|
||||
|
||||
if (idx >= 0) {
|
||||
signatureLabelOffset = idx;
|
||||
signatureLabelEnd = idx + parameter.label.length;
|
||||
}
|
||||
|
||||
// non parameter part
|
||||
element = document.createElement('span');
|
||||
element.textContent = signature.label.substring(signatureLabelEnd, end);
|
||||
dom.prepend(parent, element);
|
||||
|
||||
// parameter part
|
||||
element = document.createElement('span');
|
||||
element.className = `parameter ${i === currentParameter ? 'active' : ''}`;
|
||||
element.textContent = signature.label.substring(signatureLabelOffset, signatureLabelEnd);
|
||||
dom.prepend(parent, element);
|
||||
|
||||
end = signatureLabelOffset;
|
||||
}
|
||||
// non parameter part
|
||||
element = document.createElement('span');
|
||||
element.textContent = signature.label.substring(0, end);
|
||||
dom.prepend(parent, element);
|
||||
}
|
||||
|
||||
// private select(position: number): void {
|
||||
// const signature = this.signatureViews[position];
|
||||
|
||||
// if (!signature) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.signatures.style.height = `${ signature.height }px`;
|
||||
// this.signatures.scrollTop = signature.top;
|
||||
|
||||
// let overloads = '' + (position + 1);
|
||||
|
||||
// if (this.signatureViews.length < 10) {
|
||||
// overloads += '/' + this.signatureViews.length;
|
||||
// }
|
||||
|
||||
// this.overloads.textContent = overloads;
|
||||
|
||||
// if (this.hints && this.hints.signatures[position].parameters[this.hints.activeParameter]) {
|
||||
// const labelToAnnounce = this.hints.signatures[position].parameters[this.hints.activeParameter].label;
|
||||
// // 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.
|
||||
// if (this.announcedLabel !== labelToAnnounce) {
|
||||
// aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
|
||||
// this.announcedLabel = labelToAnnounce;
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.editor.layoutContentWidget(this);
|
||||
// }
|
||||
|
||||
next(): boolean {
|
||||
const length = this.hints.signatures.length;
|
||||
|
||||
if (length < 2) {
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentSignature = (this.currentSignature + 1) % length;
|
||||
this.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
previous(): boolean {
|
||||
const length = this.hints.signatures.length;
|
||||
|
||||
if (length < 2) {
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentSignature = (this.currentSignature - 1 + length) % length;
|
||||
this.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.model.cancel();
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return ParameterHintsWidget.ID;
|
||||
}
|
||||
|
||||
trigger(): void {
|
||||
this.model.trigger(0);
|
||||
}
|
||||
|
||||
private updateMaxHeight(): void {
|
||||
const height = Math.max(this.editor.getLayoutInfo().height / 4, 250);
|
||||
this.element.style.maxHeight = `${height}px`;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
this.model = null;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
let 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}; }`);
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget.multiple .body { border-left: 1px solid ${border.transparent(0.5)}; }`);
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid ${border.transparent(0.5)}; }`);
|
||||
|
||||
}
|
||||
let background = theme.getColor(editorHoverBackground);
|
||||
if (background) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget { background-color: ${background}; }`);
|
||||
}
|
||||
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`);
|
||||
}
|
||||
|
||||
let codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
if (codeBackground) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user