UNPKG

monaco-editor

Version:
418 lines (415 loc) 19.2 kB
import { Dimension, $, append, show, clearNode, hide, size, getClientArea } from '../../../../base/browser/dom.js'; import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; import { isHighContrast } from '../../../../platform/theme/common/theme.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (undefined && undefined.__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 = (undefined && undefined.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; function canExpandCompletionItem(item) { return !!item && Boolean(item.completion.documentation || item.completion.detail && item.completion.detail !== item.completion.label); } let SuggestDetailsWidget = class SuggestDetailsWidget { constructor(_editor, _themeService, _markdownRendererService) { this._editor = _editor; this._themeService = _themeService; this._markdownRendererService = _markdownRendererService; 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._size = new Dimension(330, 0); this.domNode = $('.suggest-details'); this.domNode.classList.add('no-docs'); this._body = $('.body'); this._scrollbar = new DomScrollableElement(this._body, { alwaysConsumeMouseWheel: true, }); append(this.domNode, this._scrollbar.getDomNode()); this._disposables.add(this._scrollbar); this._header = append(this._body, $('.header')); this._close = append(this._header, $('span' + ThemeIcon.asCSSSelector(Codicon.close))); this._close.title = localize(1542, "Close"); this._close.role = 'button'; this._close.tabIndex = -1; this._type = append(this._header, $('p.type')); this._docs = append(this._body, $('p.docs')); this._configureFont(); this._disposables.add(this._editor.onDidChangeConfiguration(e => { if (e.hasChanged(59 /* EditorOption.fontInfo */)) { this._configureFont(); } })); } dispose() { this._disposables.dispose(); this._renderDisposeable.dispose(); this._onDidClose.dispose(); this._onDidChangeContents.dispose(); } _configureFont() { const options = this._editor.getOptions(); const fontInfo = options.get(59 /* EditorOption.fontInfo */); const fontFamily = fontInfo.getMassagedFontFamily(); const fontSize = options.get(135 /* EditorOption.suggestFontSize */) || fontInfo.fontSize; const lineHeight = options.get(136 /* 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(136 /* EditorOption.suggestLineHeight */) || this._editor.getOption(59 /* EditorOption.fontInfo */).lineHeight; const borderWidth = isHighContrast(this._themeService.getColorTheme().type) ? 2 : 1; const borderHeight = borderWidth * 2; return { lineHeight, borderWidth, borderHeight, verticalPadding: 22, horizontalPadding: 14 }; } renderLoading() { this._type.textContent = localize(1543, "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; show(this._type); this._type.classList.toggle('auto-wrap', !/\r?\n^\s+/gmi.test(cappedDetail)); } else { clearNode(this._type); this._type.title = ''; hide(this._type); this.domNode.classList.add('no-type'); } // --- documentation 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'); clearNode(this._docs); const renderedContents = this._markdownRendererService.render(documentation, { context: this._editor, asyncRenderCallback: () => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); } }); this._docs.appendChild(renderedContents.element); this._renderDisposeable.add(renderedContents); } this.domNode.classList.toggle('detail-and-doc', !!detail && !!documentation); 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 Dimension(width, height); if (!Dimension.equals(newSize, this._size)) { this._size = newSize; 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); } focus() { this.domNode.focus(); } }; SuggestDetailsWidget = __decorate([ __param(1, IThemeService), __param(2, IMarkdownRendererService) ], SuggestDetailsWidget); 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 = getClientArea(this.getDomNode().ownerDocument.body); const info = this.widget.getLayoutInfo(); const defaultMinSize = new 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 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 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 southPlacement = (function () { const left = anchorBox.left; const top = -info.borderWidth + anchorBox.top + anchorBox.height; const maxSizeBottom = new 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) }; })(); // NORTH const northPlacement = (function () { const left = anchorBox.left; const maxSizeTop = new Dimension(anchorBox.width - info.borderHeight, anchorBox.top - info.verticalPadding); const top = Math.max(info.verticalPadding, anchorBox.top - size.height); return { top, left, fit: maxSizeTop.height - size.height, maxSizeTop, maxSizeBottom: maxSizeTop, minSize: defaultMinSize.with(maxSizeTop.width) }; })(); // take first placement that fits or the first with "least bad" fit // when the suggest widget is rendering above the cursor (preferAlignAtTop=false), prefer NORTH over SOUTH const verticalPlacement = preferAlignAtTop ? southPlacement : northPlacement; const placements = [eastPlacement, westPlacement, verticalPlacement]; 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 (placement === northPlacement) { // For NORTH placement, position the details above the anchor top = anchorBox.top - height + info.borderWidth; } else 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 }); // enableSashes(north, east, south, west) // For NORTH placement: enable north sash (resize upward from top), disable south (can't resize into the anchor) // Also enable west sash for horizontal resizing, consistent with SOUTH placement // For SOUTH placement and EAST/WEST placements: use existing logic based on alignAtTop if (placement === northPlacement) { this._resizable.enableSashes(true, false, false, true); } else { 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); } } export { SuggestDetailsOverlay, SuggestDetailsWidget, canExpandCompletionItem };