UNPKG

monaco-editor

Version:
526 lines (523 loc) • 22.3 kB
import { Dimension, append, $, hide, addStandardDisposableListener, show } from '../../../../../base/browser/dom.js'; import { SplitView, Sizing } from '../../../../../base/browser/ui/splitview/splitview.js'; import { Color } from '../../../../../base/common/color.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { DisposableStore, dispose } from '../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../base/common/network.js'; import { basenameOrAuthority, dirname } from '../../../../../base/common/resources.js'; import './referencesWidget.css'; import { EmbeddedCodeEditorWidget } from '../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { Range } from '../../../../common/core/range.js'; import { ModelDecorationOptions, TextModel } from '../../../../common/model/textModel.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../common/languages/modesRegistry.js'; import { ITextModelService } from '../../../../common/services/resolverService.js'; import { StringRepresentationProvider, Delegate, FileReferencesRenderer, OneReferenceRenderer, DataSource, IdentityProvider, AccessibilityProvider } from './referencesTree.js'; import { IPeekViewService, PeekViewWidget, peekViewBorder, peekViewTitleInfoForeground, peekViewTitleForeground, peekViewTitleBackground, peekViewResultsBackground } from '../../../peekView/browser/peekView.js'; import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { WorkbenchAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { FileReferences, OneReference } from '../referencesModel.js'; import { DataTransfers } from '../../../../../base/browser/dnd.js'; import { withSelection } from '../../../../../platform/opener/common/opener.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); } }; class DecorationsManager { static { this.DecorationOptions = ModelDecorationOptions.register({ description: 'reference-decoration', stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */, className: 'reference-decoration' }); } constructor(_editor, _model) { this._editor = _editor; this._model = _model; this._decorations = new Map(); this._decorationIgnoreSet = new Set(); this._callOnDispose = new DisposableStore(); this._callOnModelChange = new DisposableStore(); this._callOnDispose.add(this._editor.onDidChangeModel(() => this._onModelChanged())); this._onModelChanged(); } dispose() { this._callOnModelChange.dispose(); this._callOnDispose.dispose(); this.removeDecorations(); } _onModelChanged() { this._callOnModelChange.clear(); const model = this._editor.getModel(); if (!model) { return; } for (const ref of this._model.references) { if (ref.uri.toString() === model.uri.toString()) { this._addDecorations(ref.parent); return; } } } _addDecorations(reference) { if (!this._editor.hasModel()) { return; } this._callOnModelChange.add(this._editor.getModel().onDidChangeDecorations(() => this._onDecorationChanged())); const newDecorations = []; const newDecorationsActualIndex = []; for (let i = 0, len = reference.children.length; i < len; i++) { const oneReference = reference.children[i]; if (this._decorationIgnoreSet.has(oneReference.id)) { continue; } if (oneReference.uri.toString() !== this._editor.getModel().uri.toString()) { continue; } newDecorations.push({ range: oneReference.range, options: DecorationsManager.DecorationOptions }); newDecorationsActualIndex.push(i); } this._editor.changeDecorations((changeAccessor) => { const decorations = changeAccessor.deltaDecorations([], newDecorations); for (let i = 0; i < decorations.length; i++) { this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); } }); } _onDecorationChanged() { const toRemove = []; const model = this._editor.getModel(); if (!model) { return; } for (const [decorationId, reference] of this._decorations) { const newRange = model.getDecorationRange(decorationId); if (!newRange) { continue; } let ignore = false; if (Range.equalsRange(newRange, reference.range)) { continue; } if (Range.spansMultipleLines(newRange)) { ignore = true; } else { const lineLength = reference.range.endColumn - reference.range.startColumn; const newLineLength = newRange.endColumn - newRange.startColumn; if (lineLength !== newLineLength) { ignore = true; } } if (ignore) { this._decorationIgnoreSet.add(reference.id); toRemove.push(decorationId); } else { reference.range = newRange; } } for (let i = 0, len = toRemove.length; i < len; i++) { this._decorations.delete(toRemove[i]); } this._editor.removeDecorations(toRemove); } removeDecorations() { this._editor.removeDecorations([...this._decorations.keys()]); this._decorations.clear(); } } class LayoutData { constructor() { this.ratio = 0.7; this.heightInLines = 18; } static fromJSON(raw) { let ratio; let heightInLines; try { const data = JSON.parse(raw); ratio = data.ratio; heightInLines = data.heightInLines; } catch { // } return { ratio: ratio || 0.7, heightInLines: heightInLines || 18 }; } } class ReferencesTree extends WorkbenchAsyncDataTree { } let ReferencesDragAndDrop = class ReferencesDragAndDrop { constructor(labelService) { this.labelService = labelService; this.disposables = new DisposableStore(); } getDragURI(element) { if (element instanceof FileReferences) { return element.uri.toString(); } else if (element instanceof OneReference) { return withSelection(element.uri, element.range).toString(); } return null; } getDragLabel(elements) { if (elements.length === 0) { return undefined; } const labels = elements.map(e => this.labelService.getUriBasenameLabel(e.uri)); return labels.join(', '); } onDragStart(data, originalEvent) { if (!originalEvent.dataTransfer) { return; } const elements = data.elements; const resources = elements.map(e => this.getDragURI(e)).filter(Boolean); if (resources.length) { // Apply resources as resource-list originalEvent.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify(resources)); // Also add as plain text for outside consumers originalEvent.dataTransfer.setData(DataTransfers.TEXT, resources.join('\n')); } } onDragOver() { return false; } drop() { } dispose() { this.disposables.dispose(); } }; ReferencesDragAndDrop = __decorate([ __param(0, ILabelService) ], ReferencesDragAndDrop); /** * ZoneWidget that is shown inside the editor */ let ReferenceWidget = class ReferenceWidget extends PeekViewWidget { constructor(editor, _defaultTreeKeyboardSupport, layoutData, themeService, _textModelResolverService, _instantiationService, _peekViewService, _uriLabel, _keybindingService) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true, supportOnTitleClick: true }, _instantiationService); this._defaultTreeKeyboardSupport = _defaultTreeKeyboardSupport; this.layoutData = layoutData; this._textModelResolverService = _textModelResolverService; this._instantiationService = _instantiationService; this._peekViewService = _peekViewService; this._uriLabel = _uriLabel; this._keybindingService = _keybindingService; this._disposeOnNewModel = new DisposableStore(); this._callOnDispose = new DisposableStore(); this._onDidSelectReference = new Emitter(); this.onDidSelectReference = this._onDidSelectReference.event; this._dim = new Dimension(0, 0); this._isClosing = false; // whether or not a dispose is already in progress this._applyTheme(themeService.getColorTheme()); this._callOnDispose.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this._peekViewService.addExclusiveWidget(editor, this); this.create(); } get isClosing() { return this._isClosing; } dispose() { this._isClosing = true; this.setModel(undefined); this._callOnDispose.dispose(); this._disposeOnNewModel.dispose(); dispose(this._preview); dispose(this._previewNotAvailableMessage); dispose(this._tree); dispose(this._previewModelReference); this._splitView.dispose(); super.dispose(); } _applyTheme(theme) { const borderColor = theme.getColor(peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, frameColor: borderColor, headerBackgroundColor: theme.getColor(peekViewTitleBackground) || Color.transparent, primaryHeadingColor: theme.getColor(peekViewTitleForeground), secondaryHeadingColor: theme.getColor(peekViewTitleInfoForeground) }); } show(where) { super.show(where, this.layoutData.heightInLines || 18); } focusOnReferenceTree() { this._tree.domFocus(); } focusOnPreviewEditor() { this._preview.focus(); } isPreviewEditorFocused() { return this._preview.hasTextFocus(); } _onTitleClick(e) { if (this._preview && this._preview.getModel()) { this._onDidSelectReference.fire({ element: this._getFocusedReference(), kind: e.ctrlKey || e.metaKey || e.altKey ? 'side' : 'open', source: 'title' }); } } _fillBody(containerElement) { this.setCssClass('reference-zone-widget'); // message pane this._messageContainer = append(containerElement, $('div.messages')); hide(this._messageContainer); this._splitView = new SplitView(containerElement, { orientation: 1 /* Orientation.HORIZONTAL */ }); // editor this._previewContainer = append(containerElement, $('div.preview.inline')); const options = { scrollBeyondLastLine: false, scrollbar: { verticalScrollbarSize: 14, horizontal: 'auto', useShadows: true, verticalHasArrows: false, horizontalHasArrows: false, alwaysConsumeMouseWheel: true }, overviewRulerLanes: 2, fixedOverflowWidgets: true, minimap: { enabled: false } }; this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, {}, this.editor); hide(this._previewContainer); this._previewNotAvailableMessage = this._instantiationService.createInstance(TextModel, localize(1084, "no preview available"), PLAINTEXT_LANGUAGE_ID, TextModel.DEFAULT_CREATION_OPTIONS, null); // tree this._treeContainer = append(containerElement, $('div.ref-tree.inline')); const treeOptions = { keyboardSupport: this._defaultTreeKeyboardSupport, accessibilityProvider: new AccessibilityProvider(), keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider), identityProvider: new IdentityProvider(), openOnSingleClick: true, selectionNavigation: true, overrideStyles: { listBackground: peekViewResultsBackground }, dnd: this._instantiationService.createInstance(ReferencesDragAndDrop) }; if (this._defaultTreeKeyboardSupport) { // the tree will consume `Escape` and prevent the widget from closing this._callOnDispose.add(addStandardDisposableListener(this._treeContainer, 'keydown', (e) => { if (e.equals(9 /* KeyCode.Escape */)) { this._keybindingService.dispatchEvent(e, e.target); e.stopPropagation(); } }, true)); } this._tree = this._instantiationService.createInstance(ReferencesTree, 'ReferencesWidget', this._treeContainer, new Delegate(), [ this._instantiationService.createInstance(FileReferencesRenderer), this._instantiationService.createInstance(OneReferenceRenderer), ], this._instantiationService.createInstance(DataSource), treeOptions); // split stuff this._splitView.addView({ onDidChange: Event.None, element: this._previewContainer, minimumSize: 200, maximumSize: Number.MAX_VALUE, layout: (width) => { this._preview.layout({ height: this._dim.height, width }); } }, Sizing.Distribute); this._splitView.addView({ onDidChange: Event.None, element: this._treeContainer, minimumSize: 100, maximumSize: Number.MAX_VALUE, layout: (width) => { this._treeContainer.style.height = `${this._dim.height}px`; this._treeContainer.style.width = `${width}px`; this._tree.layout(this._dim.height, width); } }, Sizing.Distribute); this._disposables.add(this._splitView.onDidSashChange(() => { if (this._dim.width) { this.layoutData.ratio = this._splitView.getViewSize(0) / this._dim.width; } }, undefined)); // listen on selection and focus const onEvent = (element, kind) => { if (element instanceof OneReference) { if (kind === 'show') { this._revealReference(element, false); } this._onDidSelectReference.fire({ element, kind, source: 'tree' }); } }; this._disposables.add(this._tree.onDidOpen(e => { if (e.sideBySide) { onEvent(e.element, 'side'); } else if (e.editorOptions.pinned) { onEvent(e.element, 'goto'); } else { onEvent(e.element, 'show'); } })); hide(this._treeContainer); } _onWidth(width) { if (this._dim) { this._doLayoutBody(this._dim.height, width); } } _doLayoutBody(heightInPixel, widthInPixel) { super._doLayoutBody(heightInPixel, widthInPixel); this._dim = new Dimension(widthInPixel, heightInPixel); this.layoutData.heightInLines = this._viewZone ? this._viewZone.heightInLines : this.layoutData.heightInLines; this._splitView.layout(widthInPixel); this._splitView.resizeView(0, widthInPixel * this.layoutData.ratio); } setSelection(selection) { return this._revealReference(selection, true).then(() => { if (!this._model) { // disposed return; } // show in tree this._tree.setSelection([selection]); this._tree.setFocus([selection]); }); } setModel(newModel) { // clean up this._disposeOnNewModel.clear(); this._model = newModel; if (this._model) { return this._onNewModel(); } return Promise.resolve(); } _onNewModel() { if (!this._model) { return Promise.resolve(undefined); } if (this._model.isEmpty) { this.setTitle(''); this._messageContainer.innerText = localize(1085, "No results"); show(this._messageContainer); return Promise.resolve(undefined); } hide(this._messageContainer); this._decorationsManager = new DecorationsManager(this._preview, this._model); this._disposeOnNewModel.add(this._decorationsManager); // listen on model changes this._disposeOnNewModel.add(this._model.onDidChangeReferenceRange(reference => this._tree.rerender(reference))); // listen on editor this._disposeOnNewModel.add(this._preview.onMouseDown(e => { const { event, target } = e; if (event.detail !== 2) { return; } const element = this._getFocusedReference(); if (!element) { return; } this._onDidSelectReference.fire({ element: { uri: element.uri, range: target.range }, kind: (event.ctrlKey || event.metaKey || event.altKey) ? 'side' : 'open', source: 'editor' }); })); // make sure things are rendered this.container.classList.add('results-loaded'); show(this._treeContainer); show(this._previewContainer); this._splitView.layout(this._dim.width); this.focusOnReferenceTree(); // pick input and a reference to begin with return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model); } _getFocusedReference() { const [element] = this._tree.getFocus(); if (element instanceof OneReference) { return element; } else if (element instanceof FileReferences) { if (element.children.length > 0) { return element.children[0]; } } return undefined; } async revealReference(reference) { await this._revealReference(reference, false); this._onDidSelectReference.fire({ element: reference, kind: 'goto', source: 'tree' }); } async _revealReference(reference, revealParent) { // check if there is anything to do... if (this._revealedReference === reference) { return; } this._revealedReference = reference; // Update widget header if (reference.uri.scheme !== Schemas.inMemory) { this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri))); } else { this.setTitle(localize(1086, "References")); } const promise = this._textModelResolverService.createModelReference(reference.uri); if (this._tree.getInput() === reference.parent) { this._tree.reveal(reference); } else { if (revealParent) { this._tree.reveal(reference.parent); } await this._tree.expand(reference.parent); this._tree.reveal(reference); } const ref = await promise; if (!this._model) { // disposed ref.dispose(); return; } dispose(this._previewModelReference); // show in editor const model = ref.object; if (model) { const scrollType = this._preview.getModel() === model.textEditorModel ? 0 /* ScrollType.Smooth */ : 1 /* ScrollType.Immediate */; const sel = Range.lift(reference.range).collapseToStart(); this._previewModelReference = ref; this._preview.setModel(model.textEditorModel); this._preview.setSelection(sel); this._preview.revealRangeInCenter(sel, scrollType); } else { this._preview.setModel(this._previewNotAvailableMessage); ref.dispose(); } } }; ReferenceWidget = __decorate([ __param(3, IThemeService), __param(4, ITextModelService), __param(5, IInstantiationService), __param(6, IPeekViewService), __param(7, ILabelService), __param(8, IKeybindingService) ], ReferenceWidget); export { LayoutData, ReferenceWidget };