UNPKG

monaco-editor-core

Version:

A browser based code editor

388 lines (387 loc) 17.5 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import * as dom from '../../../../base/browser/dom.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { Emitter } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js'; import * as nls from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; export function canExpandCompletionItem(item) { return !!item && Boolean(item.completion.documentation || item.completion.detail && item.completion.detail !== item.completion.label); } let SuggestDetailsWidget = class SuggestDetailsWidget { constructor(_editor, instaService) { this._editor = _editor; this._onDidClose = new Emitter(); this.onDidClose = this._onDidClose.event; this._onDidChangeContents = new Emitter(); this.onDidChangeContents = this._onDidChangeContents.event; this._disposables = new DisposableStore(); this._renderDisposeable = new DisposableStore(); this._borderWidth = 1; this._size = new dom.Dimension(330, 0); this.domNode = dom.$('.suggest-details'); this.domNode.classList.add('no-docs'); this._markdownRenderer = instaService.createInstance(MarkdownRenderer, { editor: _editor }); this._body = dom.$('.body'); this._scrollbar = new DomScrollableElement(this._body, { alwaysConsumeMouseWheel: true, }); dom.append(this.domNode, this._scrollbar.getDomNode()); this._disposables.add(this._scrollbar); this._header = dom.append(this._body, dom.$('.header')); this._close = dom.append(this._header, dom.$('span' + ThemeIcon.asCSSSelector(Codicon.close))); this._close.title = nls.localize('details.close', "Close"); this._type = dom.append(this._header, dom.$('p.type')); this._docs = dom.append(this._body, dom.$('p.docs')); this._configureFont(); this._disposables.add(this._editor.onDidChangeConfiguration(e => { if (e.hasChanged(50 /* EditorOption.fontInfo */)) { this._configureFont(); } })); } dispose() { this._disposables.dispose(); this._renderDisposeable.dispose(); } _configureFont() { const options = this._editor.getOptions(); const fontInfo = options.get(50 /* EditorOption.fontInfo */); const fontFamily = fontInfo.getMassagedFontFamily(); const fontSize = options.get(120 /* EditorOption.suggestFontSize */) || fontInfo.fontSize; const lineHeight = options.get(121 /* EditorOption.suggestLineHeight */) || fontInfo.lineHeight; const fontWeight = fontInfo.fontWeight; const fontSizePx = `${fontSize}px`; const lineHeightPx = `${lineHeight}px`; this.domNode.style.fontSize = fontSizePx; this.domNode.style.lineHeight = `${lineHeight / fontSize}`; this.domNode.style.fontWeight = fontWeight; this.domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings; this._type.style.fontFamily = fontFamily; this._close.style.height = lineHeightPx; this._close.style.width = lineHeightPx; } getLayoutInfo() { const lineHeight = this._editor.getOption(121 /* EditorOption.suggestLineHeight */) || this._editor.getOption(50 /* EditorOption.fontInfo */).lineHeight; const borderWidth = this._borderWidth; const borderHeight = borderWidth * 2; return { lineHeight, borderWidth, borderHeight, verticalPadding: 22, horizontalPadding: 14 }; } renderLoading() { this._type.textContent = nls.localize('loading', "Loading..."); this._docs.textContent = ''; this.domNode.classList.remove('no-docs', 'no-type'); this.layout(this.size.width, this.getLayoutInfo().lineHeight * 2); this._onDidChangeContents.fire(this); } renderItem(item, explainMode) { this._renderDisposeable.clear(); let { detail, documentation } = item.completion; if (explainMode) { let md = ''; md += `score: ${item.score[0]}\n`; md += `prefix: ${item.word ?? '(no prefix)'}\n`; md += `word: ${item.completion.filterText ? item.completion.filterText + ' (filterText)' : item.textLabel}\n`; md += `distance: ${item.distance} (localityBonus-setting)\n`; md += `index: ${item.idx}, based on ${item.completion.sortText && `sortText: "${item.completion.sortText}"` || 'label'}\n`; md += `commit_chars: ${item.completion.commitCharacters?.join('')}\n`; documentation = new MarkdownString().appendCodeblock('empty', md); detail = `Provider: ${item.provider._debugDisplayName}`; } if (!explainMode && !canExpandCompletionItem(item)) { this.clearContents(); return; } this.domNode.classList.remove('no-docs', 'no-type'); // --- details if (detail) { const cappedDetail = detail.length > 100000 ? `${detail.substr(0, 100000)}…` : detail; this._type.textContent = cappedDetail; this._type.title = cappedDetail; dom.show(this._type); this._type.classList.toggle('auto-wrap', !/\r?\n^\s+/gmi.test(cappedDetail)); } else { dom.clearNode(this._type); this._type.title = ''; dom.hide(this._type); this.domNode.classList.add('no-type'); } // --- documentation dom.clearNode(this._docs); if (typeof documentation === 'string') { this._docs.classList.remove('markdown-docs'); this._docs.textContent = documentation; } else if (documentation) { this._docs.classList.add('markdown-docs'); dom.clearNode(this._docs); const renderedContents = this._markdownRenderer.render(documentation); this._docs.appendChild(renderedContents.element); this._renderDisposeable.add(renderedContents); this._renderDisposeable.add(this._markdownRenderer.onDidRenderAsync(() => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); })); } this.domNode.style.userSelect = 'text'; this.domNode.tabIndex = -1; this._close.onmousedown = e => { e.preventDefault(); e.stopPropagation(); }; this._close.onclick = e => { e.preventDefault(); e.stopPropagation(); this._onDidClose.fire(); }; this._body.scrollTop = 0; this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); } clearContents() { this.domNode.classList.add('no-docs'); this._type.textContent = ''; this._docs.textContent = ''; } get isEmpty() { return this.domNode.classList.contains('no-docs'); } get size() { return this._size; } layout(width, height) { const newSize = new dom.Dimension(width, height); if (!dom.Dimension.equals(newSize, this._size)) { this._size = newSize; dom.size(this.domNode, width, height); } this._scrollbar.scanDomNode(); } scrollDown(much = 8) { this._body.scrollTop += much; } scrollUp(much = 8) { this._body.scrollTop -= much; } scrollTop() { this._body.scrollTop = 0; } scrollBottom() { this._body.scrollTop = this._body.scrollHeight; } pageDown() { this.scrollDown(80); } pageUp() { this.scrollUp(80); } set borderWidth(width) { this._borderWidth = width; } get borderWidth() { return this._borderWidth; } }; SuggestDetailsWidget = __decorate([ __param(1, IInstantiationService) ], SuggestDetailsWidget); export { SuggestDetailsWidget }; export class SuggestDetailsOverlay { constructor(widget, _editor) { this.widget = widget; this._editor = _editor; this.allowEditorOverflow = true; this._disposables = new DisposableStore(); this._added = false; this._preferAlignAtTop = true; this._resizable = new ResizableHTMLElement(); this._resizable.domNode.classList.add('suggest-details-container'); this._resizable.domNode.appendChild(widget.domNode); this._resizable.enableSashes(false, true, true, false); let topLeftNow; let sizeNow; let deltaTop = 0; let deltaLeft = 0; this._disposables.add(this._resizable.onDidWillResize(() => { topLeftNow = this._topLeft; sizeNow = this._resizable.size; })); this._disposables.add(this._resizable.onDidResize(e => { if (topLeftNow && sizeNow) { this.widget.layout(e.dimension.width, e.dimension.height); let updateTopLeft = false; if (e.west) { deltaLeft = sizeNow.width - e.dimension.width; updateTopLeft = true; } if (e.north) { deltaTop = sizeNow.height - e.dimension.height; updateTopLeft = true; } if (updateTopLeft) { this._applyTopLeft({ top: topLeftNow.top + deltaTop, left: topLeftNow.left + deltaLeft, }); } } if (e.done) { topLeftNow = undefined; sizeNow = undefined; deltaTop = 0; deltaLeft = 0; this._userSize = e.dimension; } })); this._disposables.add(this.widget.onDidChangeContents(() => { if (this._anchorBox) { this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, this._preferAlignAtTop); } })); } dispose() { this._resizable.dispose(); this._disposables.dispose(); this.hide(); } getId() { return 'suggest.details'; } getDomNode() { return this._resizable.domNode; } getPosition() { return this._topLeft ? { preference: this._topLeft } : null; } show() { if (!this._added) { this._editor.addOverlayWidget(this); this._added = true; } } hide(sessionEnded = false) { this._resizable.clearSashHoverState(); if (this._added) { this._editor.removeOverlayWidget(this); this._added = false; this._anchorBox = undefined; this._topLeft = undefined; } if (sessionEnded) { this._userSize = undefined; this.widget.clearContents(); } } placeAtAnchor(anchor, preferAlignAtTop) { const anchorBox = anchor.getBoundingClientRect(); this._anchorBox = anchorBox; this._preferAlignAtTop = preferAlignAtTop; this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, preferAlignAtTop); } _placeAtAnchor(anchorBox, size, preferAlignAtTop) { const bodyBox = dom.getClientArea(this.getDomNode().ownerDocument.body); const info = this.widget.getLayoutInfo(); const defaultMinSize = new dom.Dimension(220, 2 * info.lineHeight); const defaultTop = anchorBox.top; // EAST const eastPlacement = (function () { const width = bodyBox.width - (anchorBox.left + anchorBox.width + info.borderWidth + info.horizontalPadding); const left = -info.borderWidth + anchorBox.left + anchorBox.width; const maxSizeTop = new dom.Dimension(width, bodyBox.height - anchorBox.top - info.borderHeight - info.verticalPadding); const maxSizeBottom = maxSizeTop.with(undefined, anchorBox.top + anchorBox.height - info.borderHeight - info.verticalPadding); return { top: defaultTop, left, fit: width - size.width, maxSizeTop, maxSizeBottom, minSize: defaultMinSize.with(Math.min(width, defaultMinSize.width)) }; })(); // WEST const westPlacement = (function () { const width = anchorBox.left - info.borderWidth - info.horizontalPadding; const left = Math.max(info.horizontalPadding, anchorBox.left - size.width - info.borderWidth); const maxSizeTop = new dom.Dimension(width, bodyBox.height - anchorBox.top - info.borderHeight - info.verticalPadding); const maxSizeBottom = maxSizeTop.with(undefined, anchorBox.top + anchorBox.height - info.borderHeight - info.verticalPadding); return { top: defaultTop, left, fit: width - size.width, maxSizeTop, maxSizeBottom, minSize: defaultMinSize.with(Math.min(width, defaultMinSize.width)) }; })(); // SOUTH const southPacement = (function () { const left = anchorBox.left; const top = -info.borderWidth + anchorBox.top + anchorBox.height; const maxSizeBottom = new dom.Dimension(anchorBox.width - info.borderHeight, bodyBox.height - anchorBox.top - anchorBox.height - info.verticalPadding); return { top, left, fit: maxSizeBottom.height - size.height, maxSizeBottom, maxSizeTop: maxSizeBottom, minSize: defaultMinSize.with(maxSizeBottom.width) }; })(); // take first placement that fits or the first with "least bad" fit const placements = [eastPlacement, westPlacement, southPacement]; const placement = placements.find(p => p.fit >= 0) ?? placements.sort((a, b) => b.fit - a.fit)[0]; // top/bottom placement const bottom = anchorBox.top + anchorBox.height - info.borderHeight; let alignAtTop; let height = size.height; const maxHeight = Math.max(placement.maxSizeTop.height, placement.maxSizeBottom.height); if (height > maxHeight) { height = maxHeight; } let maxSize; if (preferAlignAtTop) { if (height <= placement.maxSizeTop.height) { alignAtTop = true; maxSize = placement.maxSizeTop; } else { alignAtTop = false; maxSize = placement.maxSizeBottom; } } else { if (height <= placement.maxSizeBottom.height) { alignAtTop = false; maxSize = placement.maxSizeBottom; } else { alignAtTop = true; maxSize = placement.maxSizeTop; } } let { top, left } = placement; if (!alignAtTop && height > anchorBox.height) { top = bottom - height; } const editorDomNode = this._editor.getDomNode(); if (editorDomNode) { // get bounding rectangle of the suggest widget relative to the editor const editorBoundingBox = editorDomNode.getBoundingClientRect(); top -= editorBoundingBox.top; left -= editorBoundingBox.left; } this._applyTopLeft({ left, top }); this._resizable.enableSashes(!alignAtTop, placement === eastPlacement, alignAtTop, placement !== eastPlacement); this._resizable.minSize = placement.minSize; this._resizable.maxSize = maxSize; this._resizable.layout(height, Math.min(maxSize.width, size.width)); this.widget.layout(this._resizable.size.width, this._resizable.size.height); } _applyTopLeft(topLeft) { this._topLeft = topLeft; this._editor.layoutOverlayWidget(this); } }