UNPKG

monaco-editor-core

Version:

A browser based code editor

182 lines (181 loc) • 9.77 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 { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { derived, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; import './inlineEdit.css'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { ILanguageService } from '../../../common/languages/language.js'; import { InjectedTextCursorStops } from '../../../common/model.js'; import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js'; import { ColumnRange, applyObservableDecorations } from '../../inlineCompletions/browser/utils.js'; import { diffDeleteDecoration, diffLineDeleteDecorationBackgroundWithIndicator } from '../../../browser/widget/diffEditor/registrations.contribution.js'; export const INLINE_EDIT_DESCRIPTION = 'inline-edit'; let GhostTextWidget = class GhostTextWidget extends Disposable { constructor(editor, model, languageService) { super(); this.editor = editor; this.model = model; this.languageService = languageService; this.isDisposed = observableValue(this, false); this.currentTextModel = observableFromEvent(this, this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); this.uiState = derived(this, reader => { if (this.isDisposed.read(reader)) { return undefined; } const textModel = this.currentTextModel.read(reader); if (textModel !== this.model.targetTextModel.read(reader)) { return undefined; } const ghostText = this.model.ghostText.read(reader); if (!ghostText) { return undefined; } let range = this.model.range?.read(reader); //if range is empty, we want to remove it if (range && range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { range = undefined; } //check if both range and text are single line - in this case we want to do inline replacement //rather than replacing whole lines const isSingleLine = (range ? range.startLineNumber === range.endLineNumber : true) && ghostText.parts.length === 1 && ghostText.parts[0].lines.length === 1; //check if we're just removing code const isPureRemove = ghostText.parts.length === 1 && ghostText.parts[0].lines.every(l => l.length === 0); const inlineTexts = []; const additionalLines = []; function addToAdditionalLines(lines, className) { if (additionalLines.length > 0) { const lastLine = additionalLines[additionalLines.length - 1]; if (className) { lastLine.decorations.push(new LineDecoration(lastLine.content.length + 1, lastLine.content.length + 1 + lines[0].length, className, 0 /* InlineDecorationType.Regular */)); } lastLine.content += lines[0]; lines = lines.slice(1); } for (const line of lines) { additionalLines.push({ content: line, decorations: className ? [new LineDecoration(1, line.length + 1, className, 0 /* InlineDecorationType.Regular */)] : [] }); } } const textBufferLine = textModel.getLineContent(ghostText.lineNumber); let hiddenTextStartColumn = undefined; let lastIdx = 0; if (!isPureRemove && (isSingleLine || !range)) { for (const part of ghostText.parts) { let lines = part.lines; //If remove range is set, we want to push all new liens to virtual area if (range && !isSingleLine) { addToAdditionalLines(lines, INLINE_EDIT_DESCRIPTION); lines = []; } if (hiddenTextStartColumn === undefined) { inlineTexts.push({ column: part.column, text: lines[0], preview: part.preview, }); lines = lines.slice(1); } else { addToAdditionalLines([textBufferLine.substring(lastIdx, part.column - 1)], undefined); } if (lines.length > 0) { addToAdditionalLines(lines, INLINE_EDIT_DESCRIPTION); if (hiddenTextStartColumn === undefined && part.column <= textBufferLine.length) { hiddenTextStartColumn = part.column; } } lastIdx = part.column - 1; } if (hiddenTextStartColumn !== undefined) { addToAdditionalLines([textBufferLine.substring(lastIdx)], undefined); } } const hiddenRange = hiddenTextStartColumn !== undefined ? new ColumnRange(hiddenTextStartColumn, textBufferLine.length + 1) : undefined; const lineNumber = (isSingleLine || !range) ? ghostText.lineNumber : range.endLineNumber - 1; return { inlineTexts, additionalLines, hiddenRange, lineNumber, additionalReservedLineCount: this.model.minReservedLineCount.read(reader), targetTextModel: textModel, range, isSingleLine, isPureRemove, }; }); this.decorations = derived(this, reader => { const uiState = this.uiState.read(reader); if (!uiState) { return []; } const decorations = []; if (uiState.hiddenRange) { decorations.push({ range: uiState.hiddenRange.toRange(uiState.lineNumber), options: { inlineClassName: 'inline-edit-hidden', description: 'inline-edit-hidden', } }); } if (uiState.range) { const ranges = []; if (uiState.isSingleLine) { ranges.push(uiState.range); } else if (!uiState.isPureRemove) { const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; for (let i = 0; i < lines; i++) { const line = uiState.range.startLineNumber + i; const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); const range = new Range(line, firstNonWhitespace, line, lastNonWhitespace); ranges.push(range); } } for (const range of ranges) { decorations.push({ range, options: diffDeleteDecoration }); } } if (uiState.range && !uiState.isSingleLine && uiState.isPureRemove) { const r = new Range(uiState.range.startLineNumber, 1, uiState.range.endLineNumber - 1, 1); decorations.push({ range: r, options: diffLineDeleteDecorationBackgroundWithIndicator }); } for (const p of uiState.inlineTexts) { decorations.push({ range: Range.fromPositions(new Position(uiState.lineNumber, p.column)), options: { description: INLINE_EDIT_DESCRIPTION, after: { content: p.text, inlineClassName: p.preview ? 'inline-edit-decoration-preview' : 'inline-edit-decoration', cursorStops: InjectedTextCursorStops.Left }, showIfCollapsed: true, } }); } return decorations; }); this._register(toDisposable(() => { this.isDisposed.set(true, undefined); })); this._register(applyObservableDecorations(this.editor, this.decorations)); } }; GhostTextWidget = __decorate([ __param(2, ILanguageService) ], GhostTextWidget); export { GhostTextWidget };