monaco-editor-core
Version:
A browser based code editor
308 lines (307 loc) • 15.9 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); }
};
var InlineEditSideBySideWidget_1, InlineEditSideBySideContentWidget_1;
import { $ } from '../../../../base/browser/dom.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { ObservablePromise, autorun, autorunWithStore, derived, observableSignalFromEvent } from '../../../../base/common/observable.js';
import { derivedDisposable } from '../../../../base/common/observableInternal/derived.js';
import { URI } from '../../../../base/common/uri.js';
import './inlineEditSideBySideWidget.css';
import { observableCodeEditor } from '../../../browser/observableCodeEditor.js';
import { EmbeddedCodeEditorWidget } from '../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js';
import { IDiffProviderFactoryService } from '../../../browser/widget/diffEditor/diffProviderFactoryService.js';
import { diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from '../../../browser/widget/diffEditor/registrations.contribution.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { PLAINTEXT_LANGUAGE_ID } from '../../../common/languages/modesRegistry.js';
import { TextModel } from '../../../common/model/textModel.js';
import { IModelService } from '../../../common/services/model.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
function* range(start, end, step = 1) {
if (end === undefined) {
[end, start] = [start, 0];
}
for (let n = start; n < end; n += step) {
yield n;
}
}
function removeIndentation(lines) {
const indentation = lines[0].match(/^\s*/)?.[0] ?? '';
const length = indentation.length;
return {
text: lines.map(l => l.replace(new RegExp('^' + indentation), '')),
shift: length
};
}
let InlineEditSideBySideWidget = class InlineEditSideBySideWidget extends Disposable {
static { InlineEditSideBySideWidget_1 = this; }
static { this._modelId = 0; }
static _createUniqueUri() {
return URI.from({ scheme: 'inline-edit-widget', path: new Date().toString() + String(InlineEditSideBySideWidget_1._modelId++) });
}
constructor(_editor, _model, _instantiationService, _diffProviderFactoryService, _modelService) {
super();
this._editor = _editor;
this._model = _model;
this._instantiationService = _instantiationService;
this._diffProviderFactoryService = _diffProviderFactoryService;
this._modelService = _modelService;
this._position = derived(this, reader => {
const ghostText = this._model.read(reader);
if (!ghostText || ghostText.text.length === 0) {
return null;
}
if (ghostText.range.startLineNumber === ghostText.range.endLineNumber && !(ghostText.range.startColumn === ghostText.range.endColumn && ghostText.range.startColumn === 1)) {
//for inner-line suggestions we still want to use minimal ghost text
return null;
}
const editorModel = this._editor.getModel();
if (!editorModel) {
return null;
}
const lines = Array.from(range(ghostText.range.startLineNumber, ghostText.range.endLineNumber + 1));
const lengths = lines.map(lineNumber => editorModel.getLineLastNonWhitespaceColumn(lineNumber));
const maxColumn = Math.max(...lengths);
const lineOfMaxColumn = lines[lengths.indexOf(maxColumn)];
const position = new Position(lineOfMaxColumn, maxColumn);
const pos = {
top: ghostText.range.startLineNumber,
left: position
};
return pos;
});
this._text = derived(this, reader => {
const ghostText = this._model.read(reader);
if (!ghostText) {
return { text: '', shift: 0 };
}
const t = removeIndentation(ghostText.text.split('\n'));
return {
text: t.text.join('\n'),
shift: t.shift
};
});
this._originalModel = derivedDisposable(() => this._modelService.createModel('', null, InlineEditSideBySideWidget_1._createUniqueUri())).keepObserved(this._store);
this._modifiedModel = derivedDisposable(() => this._modelService.createModel('', null, InlineEditSideBySideWidget_1._createUniqueUri())).keepObserved(this._store);
this._diff = derived(this, reader => {
return this._diffPromise.read(reader)?.promiseResult.read(reader)?.data;
});
this._diffPromise = derived(this, reader => {
const ghostText = this._model.read(reader);
if (!ghostText) {
return;
}
const editorModel = this._editor.getModel();
if (!editorModel) {
return;
}
const originalText = removeIndentation(editorModel.getValueInRange(ghostText.range).split('\n')).text.join('\n');
const modifiedText = removeIndentation(ghostText.text.split('\n')).text.join('\n');
this._originalModel.get().setValue(originalText);
this._modifiedModel.get().setValue(modifiedText);
const d = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' });
return ObservablePromise.fromFn(async () => {
const result = await d.computeDiff(this._originalModel.get(), this._modifiedModel.get(), {
computeMoves: false,
ignoreTrimWhitespace: false,
maxComputationTimeMs: 1000,
}, CancellationToken.None);
if (result.identical) {
return undefined;
}
return result.changes;
});
});
this._register(autorunWithStore((reader, store) => {
/** @description setup content widget */
const model = this._model.read(reader);
if (!model) {
return;
}
if (this._position.get() === null) {
return;
}
const contentWidget = store.add(this._instantiationService.createInstance(InlineEditSideBySideContentWidget, this._editor, this._position, this._text.map(t => t.text), this._text.map(t => t.shift), this._diff));
_editor.addOverlayWidget(contentWidget);
store.add(toDisposable(() => _editor.removeOverlayWidget(contentWidget)));
}));
}
};
InlineEditSideBySideWidget = InlineEditSideBySideWidget_1 = __decorate([
__param(2, IInstantiationService),
__param(3, IDiffProviderFactoryService),
__param(4, IModelService)
], InlineEditSideBySideWidget);
export { InlineEditSideBySideWidget };
let InlineEditSideBySideContentWidget = class InlineEditSideBySideContentWidget extends Disposable {
static { InlineEditSideBySideContentWidget_1 = this; }
static { this.id = 0; }
constructor(_editor, _position, _text, _shift, _diff, _instantiationService) {
super();
this._editor = _editor;
this._position = _position;
this._text = _text;
this._shift = _shift;
this._diff = _diff;
this._instantiationService = _instantiationService;
this.id = `InlineEditSideBySideContentWidget${InlineEditSideBySideContentWidget_1.id++}`;
this.allowEditorOverflow = false;
this._nodes = $('div.inlineEditSideBySide', undefined);
this._scrollChanged = observableSignalFromEvent('editor.onDidScrollChange', this._editor.onDidScrollChange);
this._previewEditor = this._register(this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._nodes, {
glyphMargin: false,
lineNumbers: 'off',
minimap: { enabled: false },
guides: {
indentation: false,
bracketPairs: false,
bracketPairsHorizontal: false,
highlightActiveIndentation: false,
},
folding: false,
selectOnLineNumbers: false,
selectionHighlight: false,
columnSelection: false,
overviewRulerBorder: false,
overviewRulerLanes: 0,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
scrollbar: { vertical: 'hidden', horizontal: 'hidden', alwaysConsumeMouseWheel: false, handleMouseWheel: false },
readOnly: true,
wordWrap: 'off',
wordWrapOverride1: 'off',
wordWrapOverride2: 'off',
wrappingIndent: 'none',
wrappingStrategy: undefined,
}, { contributions: [], isSimpleWidget: true }, this._editor));
this._previewEditorObs = observableCodeEditor(this._previewEditor);
this._editorObs = observableCodeEditor(this._editor);
this._previewTextModel = this._register(this._instantiationService.createInstance(TextModel, '', this._editor.getModel()?.getLanguageId() ?? PLAINTEXT_LANGUAGE_ID, TextModel.DEFAULT_CREATION_OPTIONS, null));
this._setText = derived(reader => {
const edit = this._text.read(reader);
if (!edit) {
return;
}
this._previewTextModel.setValue(edit);
}).recomputeInitiallyAndOnChange(this._store);
this._decorations = derived(this, (reader) => {
this._setText.read(reader);
const position = this._position.read(reader);
if (!position) {
return { org: [], mod: [] };
}
const diff = this._diff.read(reader);
if (!diff) {
return { org: [], mod: [] };
}
const originalDecorations = [];
const modifiedDecorations = [];
if (diff.length === 1 && diff[0].innerChanges[0].modifiedRange.equalsRange(this._previewTextModel.getFullModelRange())) {
return { org: [], mod: [] };
}
const shift = this._shift.get();
const moveRange = (range) => {
return new Range(range.startLineNumber + position.top - 1, range.startColumn + shift, range.endLineNumber + position.top - 1, range.endColumn + shift);
};
for (const m of diff) {
if (!m.original.isEmpty) {
originalDecorations.push({ range: moveRange(m.original.toInclusiveRange()), options: diffLineDeleteDecorationBackgroundWithIndicator });
}
if (!m.modified.isEmpty) {
modifiedDecorations.push({ range: m.modified.toInclusiveRange(), options: diffLineAddDecorationBackgroundWithIndicator });
}
if (m.modified.isEmpty || m.original.isEmpty) {
if (!m.original.isEmpty) {
originalDecorations.push({ range: moveRange(m.original.toInclusiveRange()), options: diffWholeLineDeleteDecoration });
}
if (!m.modified.isEmpty) {
modifiedDecorations.push({ range: m.modified.toInclusiveRange(), options: diffWholeLineAddDecoration });
}
}
else {
for (const i of m.innerChanges || []) {
// Don't show empty markers outside the line range
if (m.original.contains(i.originalRange.startLineNumber)) {
originalDecorations.push({ range: moveRange(i.originalRange), options: i.originalRange.isEmpty() ? diffDeleteDecorationEmpty : diffDeleteDecoration });
}
if (m.modified.contains(i.modifiedRange.startLineNumber)) {
modifiedDecorations.push({ range: i.modifiedRange, options: i.modifiedRange.isEmpty() ? diffAddDecorationEmpty : diffAddDecoration });
}
}
}
}
return { org: originalDecorations, mod: modifiedDecorations };
});
this._originalDecorations = derived(this, reader => {
return this._decorations.read(reader).org;
});
this._modifiedDecorations = derived(this, reader => {
return this._decorations.read(reader).mod;
});
this._previewEditor.setModel(this._previewTextModel);
this._register(this._editorObs.setDecorations(this._originalDecorations));
this._register(this._previewEditorObs.setDecorations(this._modifiedDecorations));
this._register(autorun(reader => {
const width = this._previewEditorObs.contentWidth.read(reader);
const lines = this._text.read(reader).split('\n').length - 1;
const height = this._editor.getOption(67 /* EditorOption.lineHeight */) * lines;
if (width <= 0) {
return;
}
this._previewEditor.layout({ height: height, width: width });
}));
this._register(autorun(reader => {
/** @description update position */
this._position.read(reader);
this._editor.layoutOverlayWidget(this);
}));
this._register(autorun(reader => {
/** @description scroll change */
this._scrollChanged.read(reader);
const position = this._position.read(reader);
if (!position) {
return;
}
this._editor.layoutOverlayWidget(this);
}));
}
getId() { return this.id; }
getDomNode() {
return this._nodes;
}
getPosition() {
const position = this._position.get();
if (!position) {
return null;
}
const layoutInfo = this._editor.getLayoutInfo();
const visibPos = this._editor.getScrolledVisiblePosition(new Position(position.top, 1));
if (!visibPos) {
return null;
}
const top = visibPos.top - 1; //-1 to offset the border width
const offset = this._editor.getOffsetForColumn(position.left.lineNumber, position.left.column);
const left = layoutInfo.contentLeft + offset + 10;
return {
preference: {
left,
top,
}
};
}
};
InlineEditSideBySideContentWidget = InlineEditSideBySideContentWidget_1 = __decorate([
__param(5, IInstantiationService)
], InlineEditSideBySideContentWidget);