UNPKG

monaco-editor

Version:
268 lines (265 loc) • 15.9 kB
import { $, n } from '../../../../../../../base/browser/dom.js'; import { Emitter } from '../../../../../../../base/common/event.js'; import { Disposable } from '../../../../../../../base/common/lifecycle.js'; import '../../../../../../../base/common/observableInternal/index.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; import '../../../../../../../platform/theme/common/colors/baseColors.js'; import '../../../../../../../platform/theme/common/colors/chartsColors.js'; import { editorBackground } from '../../../../../../../platform/theme/common/colors/editorColors.js'; import '../../../../../../../platform/theme/common/colors/inputColors.js'; import '../../../../../../../platform/theme/common/colors/listColors.js'; import '../../../../../../../platform/theme/common/colors/menuColors.js'; import '../../../../../../../platform/theme/common/colors/minimapColors.js'; import '../../../../../../../platform/theme/common/colors/miscColors.js'; import '../../../../../../../platform/theme/common/colors/quickpickColors.js'; import '../../../../../../../platform/theme/common/colors/searchColors.js'; import { observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; import { RenderOptions, renderLines, LineSource } from '../../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { Rect } from '../../../../../../common/core/2d/rect.js'; import { Position } from '../../../../../../common/core/position.js'; import { Range } from '../../../../../../common/core/range.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; import { TokenArray, LineTokens } from '../../../../../../common/tokens/lineTokens.js'; import { InlineDecoration } from '../../../../../../common/viewModel/inlineDecorations.js'; import { GhostText, GhostTextPart } from '../../../model/ghostText.js'; import { GhostTextView } from '../../ghostText/ghostTextView.js'; import { getModifiedBorderColor, modifiedBackgroundColor } from '../theme.js'; import { getPrefixTrim, mapOutFalsy } from '../utils/utils.js'; import { derived } from '../../../../../../../base/common/observableInternal/observables/derived.js'; import { observableValue } from '../../../../../../../base/common/observableInternal/observables/observableValue.js'; import { constObservable } from '../../../../../../../base/common/observableInternal/observables/constObservable.js'; 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); } }; const BORDER_WIDTH = 1; const WIDGET_SEPARATOR_WIDTH = 1; const WIDGET_SEPARATOR_DIFF_EDITOR_WIDTH = 3; const BORDER_RADIUS = 4; let InlineEditsInsertionView = class InlineEditsInsertionView extends Disposable { constructor(_editor, _input, _tabAction, instantiationService, _languageService) { super(); this._editor = _editor; this._input = _input; this._tabAction = _tabAction; this._languageService = _languageService; this._onDidClick = this._register(new Emitter()); this.onDidClick = this._onDidClick.event; this._state = derived(this, reader => { const state = this._input.read(reader); if (!state) { return undefined; } const textModel = this._editor.getModel(); const eol = textModel.getEOL(); if (state.startColumn === 1 && state.lineNumber > 1 && textModel.getLineLength(state.lineNumber) !== 0 && state.text.endsWith(eol) && !state.text.startsWith(eol)) { const endOfLineColumn = textModel.getLineLength(state.lineNumber - 1) + 1; return { lineNumber: state.lineNumber - 1, column: endOfLineColumn, text: eol + state.text.slice(0, -eol.length) }; } return { lineNumber: state.lineNumber, column: state.startColumn, text: state.text }; }); this._trimVertically = derived(this, reader => { const state = this._state.read(reader); const text = state?.text; if (!text || text.trim() === '') { return { topOffset: 0, bottomOffset: 0, linesTop: 0, linesBottom: 0 }; } // Adjust for leading/trailing newlines const lineHeight = this._editor.getLineHeightForPosition(new Position(state.lineNumber, 1)); const eol = this._editor.getModel().getEOL(); let linesTop = 0; let linesBottom = 0; let i = 0; for (; i < text.length && text.startsWith(eol, i); i += eol.length) { linesTop += 1; } for (let j = text.length; j > i && text.endsWith(eol, j); j -= eol.length) { linesBottom += 1; } return { topOffset: linesTop * lineHeight, bottomOffset: linesBottom * lineHeight, linesTop, linesBottom }; }); this._maxPrefixTrim = derived(this, reader => { const state = this._state.read(reader); if (!state) { return { prefixLeftOffset: 0, prefixTrim: 0 }; } const textModel = this._editor.getModel(); const eol = textModel.getEOL(); const trimVertically = this._trimVertically.read(reader); const lines = state.text.split(eol); const modifiedLines = lines.slice(trimVertically.linesTop, lines.length - trimVertically.linesBottom); if (trimVertically.linesTop === 0) { modifiedLines[0] = textModel.getLineContent(state.lineNumber) + modifiedLines[0]; } const originalRange = new LineRange(state.lineNumber, state.lineNumber + (trimVertically.linesTop > 0 ? 0 : 1)); return getPrefixTrim([], originalRange, modifiedLines, this._editor); }); this._ghostText = derived(reader => { const state = this._state.read(reader); const prefixTrim = this._maxPrefixTrim.read(reader); if (!state) { return undefined; } const textModel = this._editor.getModel(); const eol = textModel.getEOL(); const modifiedLines = state.text.split(eol); const inlineDecorations = modifiedLines.map((line, i) => new InlineDecoration(new Range(i + 1, i === 0 ? 1 : prefixTrim.prefixTrim + 1, i + 1, line.length + 1), 'modified-background', 0 /* InlineDecorationType.Regular */)); return new GhostText(state.lineNumber, [new GhostTextPart(state.column, state.text, false, inlineDecorations)]); }); this._display = derived(this, reader => !!this._state.read(reader) ? 'block' : 'none'); this._editorMaxContentWidthInRange = derived(this, reader => { const state = this._state.read(reader); if (!state) { return 0; } this._editorObs.versionId.read(reader); const textModel = this._editor.getModel(); const eol = textModel.getEOL(); const textBeforeInsertion = state.text.startsWith(eol) ? '' : textModel.getValueInRange(new Range(state.lineNumber, 1, state.lineNumber, state.column)); const textAfterInsertion = textModel.getValueInRange(new Range(state.lineNumber, state.column, state.lineNumber, textModel.getLineLength(state.lineNumber) + 1)); const text = textBeforeInsertion + state.text + textAfterInsertion; const lines = text.split(eol); const renderOptions = RenderOptions.fromEditor(this._editor).withSetWidth(false).withScrollBeyondLastColumn(0); const lineWidths = lines.map(line => { const t = textModel.tokenization.tokenizeLinesAt(state.lineNumber, [line])?.[0]; let tokens; if (t) { tokens = TokenArray.fromLineTokens(t).toLineTokens(line, this._languageService.languageIdCodec); } else { tokens = LineTokens.createEmpty(line, this._languageService.languageIdCodec); } return renderLines(new LineSource([tokens]), renderOptions, [], $('div'), true).minWidthInPx; }); // Take the max value that we observed. // Reset when either the edit changes or the editor text version. return Math.max(...lineWidths); }); this.startLineOffset = this._trimVertically.map(v => v.topOffset); this.originalLines = this._state.map(s => s ? new LineRange(s.lineNumber, Math.min(s.lineNumber + 2, this._editor.getModel().getLineCount() + 1)) : undefined); this._overlayLayout = derived(this, (reader) => { this._ghostText.read(reader); const state = this._state.read(reader); if (!state) { return null; } // Update the overlay when the position changes this._editorObs.observePosition(observableValue(this, new Position(state.lineNumber, state.column)), reader.store).read(reader); const editorLayout = this._editorObs.layoutInfo.read(reader); const horizontalScrollOffset = this._editorObs.scrollLeft.read(reader); const verticalScrollbarWidth = this._editorObs.layoutInfoVerticalScrollbarWidth.read(reader); const right = editorLayout.contentLeft + this._editorMaxContentWidthInRange.read(reader) - horizontalScrollOffset; const prefixLeftOffset = this._maxPrefixTrim.read(reader).prefixLeftOffset ?? 0 /* fix due to observable bug? */; const left = editorLayout.contentLeft + prefixLeftOffset - horizontalScrollOffset; if (right <= left) { return null; } const { topOffset: topTrim, bottomOffset: bottomTrim } = this._trimVertically.read(reader); const scrollTop = this._editorObs.scrollTop.read(reader); const height = this._ghostTextView.height.read(reader) - topTrim - bottomTrim; const top = this._editor.getTopForLineNumber(state.lineNumber) - scrollTop + topTrim; const bottom = top + height; const overlay = new Rect(left, top, right, bottom); return { overlay, startsAtContentLeft: prefixLeftOffset === 0, contentLeft: editorLayout.contentLeft, minContentWidthRequired: prefixLeftOffset + overlay.width + verticalScrollbarWidth, }; }).recomputeInitiallyAndOnChange(this._store); this._modifiedOverlay = n.div({ style: { pointerEvents: 'none', } }, derived(this, reader => { const overlayLayoutObs = mapOutFalsy(this._overlayLayout).read(reader); if (!overlayLayoutObs) { return undefined; } // Create an overlay which hides the left hand side of the original overlay when it overflows to the left // such that there is a smooth transition at the edge of content left const overlayHider = overlayLayoutObs.map(layoutInfo => Rect.fromLeftTopRightBottom(layoutInfo.contentLeft - BORDER_RADIUS - BORDER_WIDTH, layoutInfo.overlay.top, layoutInfo.contentLeft, layoutInfo.overlay.bottom)).read(reader); const separatorWidth = this._input.map(i => i?.inDiffEditor ? WIDGET_SEPARATOR_DIFF_EDITOR_WIDTH : WIDGET_SEPARATOR_WIDTH).read(reader); const overlayRect = overlayLayoutObs.map(l => l.overlay.withMargin(0, BORDER_WIDTH, 0, l.startsAtContentLeft ? 0 : BORDER_WIDTH).intersectHorizontal(new OffsetRange(overlayHider.left, Number.MAX_SAFE_INTEGER))); const underlayRect = overlayRect.map(rect => rect.withMargin(separatorWidth, separatorWidth)); return [ n.div({ class: 'originalUnderlayInsertion', style: { ...underlayRect.read(reader).toStyles(), borderRadius: BORDER_RADIUS, border: `${BORDER_WIDTH + separatorWidth}px solid ${asCssVariable(editorBackground)}`, boxSizing: 'border-box', } }), n.div({ class: 'originalOverlayInsertion', style: { ...overlayRect.read(reader).toStyles(), borderRadius: BORDER_RADIUS, border: getModifiedBorderColor(this._tabAction).map(bc => `${BORDER_WIDTH}px solid ${asCssVariable(bc)}`), boxSizing: 'border-box', backgroundColor: asCssVariable(modifiedBackgroundColor), } }), n.div({ class: 'originalOverlayHiderInsertion', style: { ...overlayHider.toStyles(), backgroundColor: asCssVariable(editorBackground), } }) ]; })).keepUpdated(this._store); this._view = n.div({ class: 'inline-edits-view', style: { position: 'absolute', overflow: 'visible', top: '0px', left: '0px', display: this._display, }, }, [ [this._modifiedOverlay], ]).keepUpdated(this._store); this._editorObs = observableCodeEditor(this._editor); this._ghostTextView = this._register(instantiationService.createInstance(GhostTextView, this._editor, { ghostText: this._ghostText, minReservedLineCount: constObservable(0), targetTextModel: this._editorObs.model.map(model => model ?? undefined), warning: constObservable(undefined), handleInlineCompletionShown: constObservable(() => { // This is a no-op for the insertion view, as it is handled by the InlineEditsView. }), }, observableValue(this, { syntaxHighlightingEnabled: true, extraClasses: ['inline-edit'] }), true, true)); this.isHovered = this._ghostTextView.isHovered; this._register(this._ghostTextView.onDidClick((e) => { this._onDidClick.fire(e); })); this._register(this._editorObs.createOverlayWidget({ domNode: this._view.element, position: constObservable(null), allowEditorOverflow: false, minContentWidthInPx: derived(this, reader => { const info = this._overlayLayout.read(reader); if (info === null) { return 0; } return info.minContentWidthRequired; }), })); } }; InlineEditsInsertionView = __decorate([ __param(3, IInstantiationService), __param(4, ILanguageService) ], InlineEditsInsertionView); export { InlineEditsInsertionView };