UNPKG

monaco-editor

Version:
429 lines (428 loc) • 24.3 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); } }; var HideUnchangedRegionsFeature_1; import { $, addDisposableListener, getWindow, h, reset } from '../../../../../base/browser/dom.js'; import { renderIcon, renderLabelWithIcons } from '../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { autorun, derived, derivedWithStore, observableValue, transaction } from '../../../../../base/common/observable.js'; import { derivedDisposable } from '../../../../../base/common/observableInternal/derived.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { isDefined } from '../../../../../base/common/types.js'; import { observableCodeEditor } from '../../../observableCodeEditor.js'; import { PlaceholderViewZone, ViewZoneOverlayWidget, applyObservableDecorations, applyStyle } from '../utils.js'; import { LineRange } from '../../../../common/core/lineRange.js'; import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; import { SymbolKinds } from '../../../../common/languages.js'; import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; /** * Make sure to add the view zones to the editor! */ let HideUnchangedRegionsFeature = class HideUnchangedRegionsFeature extends Disposable { static { HideUnchangedRegionsFeature_1 = this; } static { this._breadcrumbsSourceFactory = observableValue(HideUnchangedRegionsFeature_1, () => ({ dispose() { }, getBreadcrumbItems(startRange, reader) { return []; }, })); } static setBreadcrumbsSourceFactory(factory) { this._breadcrumbsSourceFactory.set(factory, undefined); } get isUpdatingHiddenAreas() { return this._isUpdatingHiddenAreas; } constructor(_editors, _diffModel, _options, _instantiationService) { super(); this._editors = _editors; this._diffModel = _diffModel; this._options = _options; this._instantiationService = _instantiationService; this._modifiedOutlineSource = derivedDisposable(this, (reader) => { const m = this._editors.modifiedModel.read(reader); const factory = HideUnchangedRegionsFeature_1._breadcrumbsSourceFactory.read(reader); return (!m || !factory) ? undefined : factory(m, this._instantiationService); }); this._isUpdatingHiddenAreas = false; this._register(this._editors.original.onDidChangeCursorPosition(e => { if (e.reason === 1 /* CursorChangeReason.ContentFlush */) { return; } const m = this._diffModel.get(); transaction(tx => { for (const s of this._editors.original.getSelections() || []) { m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, 0 /* RevealPreference.FromCloserSide */, tx); m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, 0 /* RevealPreference.FromCloserSide */, tx); } }); })); this._register(this._editors.modified.onDidChangeCursorPosition(e => { if (e.reason === 1 /* CursorChangeReason.ContentFlush */) { return; } const m = this._diffModel.get(); transaction(tx => { for (const s of this._editors.modified.getSelections() || []) { m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, 0 /* RevealPreference.FromCloserSide */, tx); m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, 0 /* RevealPreference.FromCloserSide */, tx); } }); })); const unchangedRegions = this._diffModel.map((m, reader) => { const regions = m?.unchangedRegions.read(reader) ?? []; if (regions.length === 1 && regions[0].modifiedLineNumber === 1 && regions[0].lineCount === this._editors.modifiedModel.read(reader)?.getLineCount()) { return []; } return regions; }); this.viewZones = derivedWithStore(this, (reader, store) => { /** @description view Zones */ const modifiedOutlineSource = this._modifiedOutlineSource.read(reader); if (!modifiedOutlineSource) { return { origViewZones: [], modViewZones: [] }; } const origViewZones = []; const modViewZones = []; const sideBySide = this._options.renderSideBySide.read(reader); const compactMode = this._options.compactMode.read(reader); const curUnchangedRegions = unchangedRegions.read(reader); for (let i = 0; i < curUnchangedRegions.length; i++) { const r = curUnchangedRegions[i]; if (r.shouldHideControls(reader)) { continue; } if (compactMode && (i === 0 || i === curUnchangedRegions.length - 1)) { continue; } if (compactMode) { { const d = derived(this, reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); const origVz = new PlaceholderViewZone(d, 12); origViewZones.push(origVz); store.add(new CompactCollapsedCodeOverlayWidget(this._editors.original, origVz, r, !sideBySide)); } { const d = derived(this, reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); const modViewZone = new PlaceholderViewZone(d, 12); modViewZones.push(modViewZone); store.add(new CompactCollapsedCodeOverlayWidget(this._editors.modified, modViewZone, r)); } } else { { const d = derived(this, reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); const origVz = new PlaceholderViewZone(d, 24); origViewZones.push(origVz); store.add(new CollapsedCodeOverlayWidget(this._editors.original, origVz, r, r.originalUnchangedRange, !sideBySide, modifiedOutlineSource, l => this._diffModel.get().ensureModifiedLineIsVisible(l, 2 /* RevealPreference.FromBottom */, undefined), this._options)); } { const d = derived(this, reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); const modViewZone = new PlaceholderViewZone(d, 24); modViewZones.push(modViewZone); store.add(new CollapsedCodeOverlayWidget(this._editors.modified, modViewZone, r, r.modifiedUnchangedRange, false, modifiedOutlineSource, l => this._diffModel.get().ensureModifiedLineIsVisible(l, 2 /* RevealPreference.FromBottom */, undefined), this._options)); } } } return { origViewZones, modViewZones, }; }); const unchangedLinesDecoration = { description: 'unchanged lines', className: 'diff-unchanged-lines', isWholeLine: true, }; const unchangedLinesDecorationShow = { description: 'Fold Unchanged', glyphMarginHoverMessage: new MarkdownString(undefined, { isTrusted: true, supportThemeIcons: true }) .appendMarkdown(localize('foldUnchanged', 'Fold Unchanged Region')), glyphMarginClassName: 'fold-unchanged ' + ThemeIcon.asClassName(Codicon.fold), zIndex: 10001, }; this._register(applyObservableDecorations(this._editors.original, derived(this, reader => { /** @description decorations */ const curUnchangedRegions = unchangedRegions.read(reader); const result = curUnchangedRegions.map(r => ({ range: r.originalUnchangedRange.toInclusiveRange(), options: unchangedLinesDecoration, })); for (const r of curUnchangedRegions) { if (r.shouldHideControls(reader)) { result.push({ range: Range.fromPositions(new Position(r.originalLineNumber, 1)), options: unchangedLinesDecorationShow, }); } } return result; }))); this._register(applyObservableDecorations(this._editors.modified, derived(this, reader => { /** @description decorations */ const curUnchangedRegions = unchangedRegions.read(reader); const result = curUnchangedRegions.map(r => ({ range: r.modifiedUnchangedRange.toInclusiveRange(), options: unchangedLinesDecoration, })); for (const r of curUnchangedRegions) { if (r.shouldHideControls(reader)) { result.push({ range: LineRange.ofLength(r.modifiedLineNumber, 1).toInclusiveRange(), options: unchangedLinesDecorationShow, }); } } return result; }))); this._register(autorun((reader) => { /** @description update folded unchanged regions */ const curUnchangedRegions = unchangedRegions.read(reader); this._isUpdatingHiddenAreas = true; try { this._editors.original.setHiddenAreas(curUnchangedRegions.map(r => r.getHiddenOriginalRange(reader).toInclusiveRange()).filter(isDefined)); this._editors.modified.setHiddenAreas(curUnchangedRegions.map(r => r.getHiddenModifiedRange(reader).toInclusiveRange()).filter(isDefined)); } finally { this._isUpdatingHiddenAreas = false; } })); this._register(this._editors.modified.onMouseUp(event => { if (!event.event.rightButton && event.target.position && event.target.element?.className.includes('fold-unchanged')) { const lineNumber = event.target.position.lineNumber; const model = this._diffModel.get(); if (!model) { return; } const region = model.unchangedRegions.get().find(r => r.modifiedUnchangedRange.includes(lineNumber)); if (!region) { return; } region.collapseAll(undefined); event.event.stopPropagation(); event.event.preventDefault(); } })); this._register(this._editors.original.onMouseUp(event => { if (!event.event.rightButton && event.target.position && event.target.element?.className.includes('fold-unchanged')) { const lineNumber = event.target.position.lineNumber; const model = this._diffModel.get(); if (!model) { return; } const region = model.unchangedRegions.get().find(r => r.originalUnchangedRange.includes(lineNumber)); if (!region) { return; } region.collapseAll(undefined); event.event.stopPropagation(); event.event.preventDefault(); } })); } }; HideUnchangedRegionsFeature = HideUnchangedRegionsFeature_1 = __decorate([ __param(3, IInstantiationService) ], HideUnchangedRegionsFeature); export { HideUnchangedRegionsFeature }; class CompactCollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { constructor(editor, _viewZone, _unchangedRegion, _hide = false) { const root = h('div.diff-hidden-lines-widget'); super(editor, _viewZone, root.root); this._unchangedRegion = _unchangedRegion; this._hide = _hide; this._nodes = h('div.diff-hidden-lines-compact', [ h('div.line-left', []), h('div.text@text', []), h('div.line-right', []) ]); root.root.appendChild(this._nodes.root); if (this._hide) { this._nodes.root.replaceChildren(); } this._register(autorun(reader => { /** @description update labels */ if (!this._hide) { const lineCount = this._unchangedRegion.getHiddenModifiedRange(reader).length; const linesHiddenText = localize('hiddenLines', '{0} hidden lines', lineCount); this._nodes.text.innerText = linesHiddenText; } })); } } class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { constructor(_editor, _viewZone, _unchangedRegion, _unchangedRegionRange, _hide, _modifiedOutlineSource, _revealModifiedHiddenLine, _options) { const root = h('div.diff-hidden-lines-widget'); super(_editor, _viewZone, root.root); this._editor = _editor; this._unchangedRegion = _unchangedRegion; this._unchangedRegionRange = _unchangedRegionRange; this._hide = _hide; this._modifiedOutlineSource = _modifiedOutlineSource; this._revealModifiedHiddenLine = _revealModifiedHiddenLine; this._options = _options; this._nodes = h('div.diff-hidden-lines', [ h('div.top@top', { title: localize('diff.hiddenLines.top', 'Click or drag to show more above') }), h('div.center@content', { style: { display: 'flex' } }, [ h('div@first', { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', flexShrink: '0' } }, [$('a', { title: localize('showUnchangedRegion', 'Show Unchanged Region'), role: 'button', onclick: () => { this._unchangedRegion.showAll(undefined); } }, ...renderLabelWithIcons('$(unfold)'))]), h('div@others', { style: { display: 'flex', justifyContent: 'center', alignItems: 'center' } }), ]), h('div.bottom@bottom', { title: localize('diff.bottom', 'Click or drag to show more below'), role: 'button' }), ]); root.root.appendChild(this._nodes.root); if (!this._hide) { this._register(applyStyle(this._nodes.first, { width: observableCodeEditor(this._editor).layoutInfoContentLeft })); } else { reset(this._nodes.first); } this._register(autorun(reader => { /** @description Update CollapsedCodeOverlayWidget canMove* css classes */ const isFullyRevealed = this._unchangedRegion.visibleLineCountTop.read(reader) + this._unchangedRegion.visibleLineCountBottom.read(reader) === this._unchangedRegion.lineCount; this._nodes.bottom.classList.toggle('canMoveTop', !isFullyRevealed); this._nodes.bottom.classList.toggle('canMoveBottom', this._unchangedRegion.visibleLineCountBottom.read(reader) > 0); this._nodes.top.classList.toggle('canMoveTop', this._unchangedRegion.visibleLineCountTop.read(reader) > 0); this._nodes.top.classList.toggle('canMoveBottom', !isFullyRevealed); const isDragged = this._unchangedRegion.isDragged.read(reader); const domNode = this._editor.getDomNode(); if (domNode) { domNode.classList.toggle('draggingUnchangedRegion', !!isDragged); if (isDragged === 'top') { domNode.classList.toggle('canMoveTop', this._unchangedRegion.visibleLineCountTop.read(reader) > 0); domNode.classList.toggle('canMoveBottom', !isFullyRevealed); } else if (isDragged === 'bottom') { domNode.classList.toggle('canMoveTop', !isFullyRevealed); domNode.classList.toggle('canMoveBottom', this._unchangedRegion.visibleLineCountBottom.read(reader) > 0); } else { domNode.classList.toggle('canMoveTop', false); domNode.classList.toggle('canMoveBottom', false); } } })); const editor = this._editor; this._register(addDisposableListener(this._nodes.top, 'mousedown', e => { if (e.button !== 0) { return; } this._nodes.top.classList.toggle('dragging', true); this._nodes.root.classList.toggle('dragging', true); e.preventDefault(); const startTop = e.clientY; let didMove = false; const cur = this._unchangedRegion.visibleLineCountTop.get(); this._unchangedRegion.isDragged.set('top', undefined); const window = getWindow(this._nodes.top); const mouseMoveListener = addDisposableListener(window, 'mousemove', e => { const currentTop = e.clientY; const delta = currentTop - startTop; didMove = didMove || Math.abs(delta) > 2; const lineDelta = Math.round(delta / editor.getOption(67 /* EditorOption.lineHeight */)); const newVal = Math.max(0, Math.min(cur + lineDelta, this._unchangedRegion.getMaxVisibleLineCountTop())); this._unchangedRegion.visibleLineCountTop.set(newVal, undefined); }); const mouseUpListener = addDisposableListener(window, 'mouseup', e => { if (!didMove) { this._unchangedRegion.showMoreAbove(this._options.hideUnchangedRegionsRevealLineCount.get(), undefined); } this._nodes.top.classList.toggle('dragging', false); this._nodes.root.classList.toggle('dragging', false); this._unchangedRegion.isDragged.set(undefined, undefined); mouseMoveListener.dispose(); mouseUpListener.dispose(); }); })); this._register(addDisposableListener(this._nodes.bottom, 'mousedown', e => { if (e.button !== 0) { return; } this._nodes.bottom.classList.toggle('dragging', true); this._nodes.root.classList.toggle('dragging', true); e.preventDefault(); const startTop = e.clientY; let didMove = false; const cur = this._unchangedRegion.visibleLineCountBottom.get(); this._unchangedRegion.isDragged.set('bottom', undefined); const window = getWindow(this._nodes.bottom); const mouseMoveListener = addDisposableListener(window, 'mousemove', e => { const currentTop = e.clientY; const delta = currentTop - startTop; didMove = didMove || Math.abs(delta) > 2; const lineDelta = Math.round(delta / editor.getOption(67 /* EditorOption.lineHeight */)); const newVal = Math.max(0, Math.min(cur - lineDelta, this._unchangedRegion.getMaxVisibleLineCountBottom())); const top = this._unchangedRegionRange.endLineNumberExclusive > editor.getModel().getLineCount() ? editor.getContentHeight() : editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); this._unchangedRegion.visibleLineCountBottom.set(newVal, undefined); const top2 = this._unchangedRegionRange.endLineNumberExclusive > editor.getModel().getLineCount() ? editor.getContentHeight() : editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); editor.setScrollTop(editor.getScrollTop() + (top2 - top)); }); const mouseUpListener = addDisposableListener(window, 'mouseup', e => { this._unchangedRegion.isDragged.set(undefined, undefined); if (!didMove) { const top = editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); this._unchangedRegion.showMoreBelow(this._options.hideUnchangedRegionsRevealLineCount.get(), undefined); const top2 = editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); editor.setScrollTop(editor.getScrollTop() + (top2 - top)); } this._nodes.bottom.classList.toggle('dragging', false); this._nodes.root.classList.toggle('dragging', false); mouseMoveListener.dispose(); mouseUpListener.dispose(); }); })); this._register(autorun(reader => { /** @description update labels */ const children = []; if (!this._hide) { const lineCount = _unchangedRegion.getHiddenModifiedRange(reader).length; const linesHiddenText = localize('hiddenLines', '{0} hidden lines', lineCount); const span = $('span', { title: localize('diff.hiddenLines.expandAll', 'Double click to unfold') }, linesHiddenText); span.addEventListener('dblclick', e => { if (e.button !== 0) { return; } e.preventDefault(); this._unchangedRegion.showAll(undefined); }); children.push(span); const range = this._unchangedRegion.getHiddenModifiedRange(reader); const items = this._modifiedOutlineSource.getBreadcrumbItems(range, reader); if (items.length > 0) { children.push($('span', undefined, '\u00a0\u00a0|\u00a0\u00a0')); for (let i = 0; i < items.length; i++) { const item = items[i]; const icon = SymbolKinds.toIcon(item.kind); const divItem = h('div.breadcrumb-item', { style: { display: 'flex', alignItems: 'center' }, }, [ renderIcon(icon), '\u00a0', item.name, ...(i === items.length - 1 ? [] : [renderIcon(Codicon.chevronRight)]) ]).root; children.push(divItem); divItem.onclick = () => { this._revealModifiedHiddenLine(item.startLineNumber); }; } } } reset(this._nodes.others, ...children); })); } }