monaco-editor
Version:
A browser based code editor
256 lines (255 loc) • 13.3 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 { createTrustedTypesPolicy } from '../../../../base/browser/trustedTypes.js';
import { Event } from '../../../../base/common/event.js';
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { autorun, derived, observableFromEvent, observableSignalFromEvent, observableValue } from '../../../../base/common/observable.js';
import * as strings from '../../../../base/common/strings.js';
import './ghostText.css';
import { applyFontInfo } from '../../../browser/config/domFontInfo.js';
import { EditorFontLigatures } from '../../../common/config/editorOptions.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { StringBuilder } from '../../../common/core/stringBuilder.js';
import { ILanguageService } from '../../../common/languages/language.js';
import { InjectedTextCursorStops } from '../../../common/model.js';
import { LineTokens } from '../../../common/tokens/lineTokens.js';
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
import { RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
import { GhostTextReplacement } from './ghostText.js';
import { ColumnRange, applyObservableDecorations } from './utils.js';
export const GHOST_TEXT_DESCRIPTION = 'ghost-text';
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.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;
}
const replacedRange = ghostText instanceof GhostTextReplacement ? ghostText.columnRange : undefined;
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;
for (const part of ghostText.parts) {
let lines = part.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, GHOST_TEXT_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;
return {
replacedRange,
inlineTexts,
additionalLines,
hiddenRange,
lineNumber: ghostText.lineNumber,
additionalReservedLineCount: this.model.minReservedLineCount.read(reader),
targetTextModel: textModel,
};
});
this.decorations = derived(this, reader => {
const uiState = this.uiState.read(reader);
if (!uiState) {
return [];
}
const decorations = [];
if (uiState.replacedRange) {
decorations.push({
range: uiState.replacedRange.toRange(uiState.lineNumber),
options: { inlineClassName: 'inline-completion-text-to-replace', description: 'GhostTextReplacement' }
});
}
if (uiState.hiddenRange) {
decorations.push({
range: uiState.hiddenRange.toRange(uiState.lineNumber),
options: { inlineClassName: 'ghost-text-hidden', description: 'ghost-text-hidden', }
});
}
for (const p of uiState.inlineTexts) {
decorations.push({
range: Range.fromPositions(new Position(uiState.lineNumber, p.column)),
options: {
description: GHOST_TEXT_DESCRIPTION,
after: { content: p.text, inlineClassName: p.preview ? 'ghost-text-decoration-preview' : 'ghost-text-decoration', cursorStops: InjectedTextCursorStops.Left },
showIfCollapsed: true,
}
});
}
return decorations;
});
this.additionalLinesWidget = this._register(new AdditionalLinesWidget(this.editor, this.languageService.languageIdCodec, derived(reader => {
/** @description lines */
const uiState = this.uiState.read(reader);
return uiState ? {
lineNumber: uiState.lineNumber,
additionalLines: uiState.additionalLines,
minReservedLineCount: uiState.additionalReservedLineCount,
targetTextModel: uiState.targetTextModel,
} : undefined;
})));
this._register(toDisposable(() => { this.isDisposed.set(true, undefined); }));
this._register(applyObservableDecorations(this.editor, this.decorations));
}
ownsViewZone(viewZoneId) {
return this.additionalLinesWidget.viewZoneId === viewZoneId;
}
};
GhostTextWidget = __decorate([
__param(2, ILanguageService)
], GhostTextWidget);
export { GhostTextWidget };
class AdditionalLinesWidget extends Disposable {
get viewZoneId() { return this._viewZoneId; }
constructor(editor, languageIdCodec, lines) {
super();
this.editor = editor;
this.languageIdCodec = languageIdCodec;
this.lines = lines;
this._viewZoneId = undefined;
this.editorOptionsChanged = observableSignalFromEvent('editorOptionChanged', Event.filter(this.editor.onDidChangeConfiguration, e => e.hasChanged(33 /* EditorOption.disableMonospaceOptimizations */)
|| e.hasChanged(116 /* EditorOption.stopRenderingLineAfter */)
|| e.hasChanged(98 /* EditorOption.renderWhitespace */)
|| e.hasChanged(93 /* EditorOption.renderControlCharacters */)
|| e.hasChanged(51 /* EditorOption.fontLigatures */)
|| e.hasChanged(50 /* EditorOption.fontInfo */)
|| e.hasChanged(66 /* EditorOption.lineHeight */)));
this._register(autorun(reader => {
/** @description update view zone */
const lines = this.lines.read(reader);
this.editorOptionsChanged.read(reader);
if (lines) {
this.updateLines(lines.lineNumber, lines.additionalLines, lines.minReservedLineCount);
}
else {
this.clear();
}
}));
}
dispose() {
super.dispose();
this.clear();
}
clear() {
this.editor.changeViewZones((changeAccessor) => {
if (this._viewZoneId) {
changeAccessor.removeZone(this._viewZoneId);
this._viewZoneId = undefined;
}
});
}
updateLines(lineNumber, additionalLines, minReservedLineCount) {
const textModel = this.editor.getModel();
if (!textModel) {
return;
}
const { tabSize } = textModel.getOptions();
this.editor.changeViewZones((changeAccessor) => {
if (this._viewZoneId) {
changeAccessor.removeZone(this._viewZoneId);
this._viewZoneId = undefined;
}
const heightInLines = Math.max(additionalLines.length, minReservedLineCount);
if (heightInLines > 0) {
const domNode = document.createElement('div');
renderLines(domNode, tabSize, additionalLines, this.editor.getOptions(), this.languageIdCodec);
this._viewZoneId = changeAccessor.addZone({
afterLineNumber: lineNumber,
heightInLines: heightInLines,
domNode,
afterColumnAffinity: 1 /* PositionAffinity.Right */
});
}
});
}
}
function renderLines(domNode, tabSize, lines, opts, languageIdCodec) {
const disableMonospaceOptimizations = opts.get(33 /* EditorOption.disableMonospaceOptimizations */);
const stopRenderingLineAfter = opts.get(116 /* EditorOption.stopRenderingLineAfter */);
// To avoid visual confusion, we don't want to render visible whitespace
const renderWhitespace = 'none';
const renderControlCharacters = opts.get(93 /* EditorOption.renderControlCharacters */);
const fontLigatures = opts.get(51 /* EditorOption.fontLigatures */);
const fontInfo = opts.get(50 /* EditorOption.fontInfo */);
const lineHeight = opts.get(66 /* EditorOption.lineHeight */);
const sb = new StringBuilder(10000);
sb.appendString('<div class="suggest-preview-text">');
for (let i = 0, len = lines.length; i < len; i++) {
const lineData = lines[i];
const line = lineData.content;
sb.appendString('<div class="view-line');
sb.appendString('" style="top:');
sb.appendString(String(i * lineHeight));
sb.appendString('px;width:1000000px;">');
const isBasicASCII = strings.isBasicASCII(line);
const containsRTL = strings.containsRTL(line);
const lineTokens = LineTokens.createEmpty(line, languageIdCodec);
renderViewLine(new RenderLineInput((fontInfo.isMonospace && !disableMonospaceOptimizations), fontInfo.canUseHalfwidthRightwardsArrow, line, false, isBasicASCII, containsRTL, 0, lineTokens, lineData.decorations, tabSize, 0, fontInfo.spaceWidth, fontInfo.middotWidth, fontInfo.wsmiddotWidth, stopRenderingLineAfter, renderWhitespace, renderControlCharacters, fontLigatures !== EditorFontLigatures.OFF, null), sb);
sb.appendString('</div>');
}
sb.appendString('</div>');
applyFontInfo(domNode, fontInfo);
const html = sb.build();
const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html;
domNode.innerHTML = trustedhtml;
}
const ttPolicy = createTrustedTypesPolicy('editorGhostText', { createHTML: value => value });