monaco-editor-core
Version:
A browser based code editor
182 lines (181 loc) • 9.77 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 };