monaco-editor
Version:
A browser based code editor
193 lines (190 loc) • 11.3 kB
JavaScript
import { createStyleSheetFromObservable } from '../../../../../base/browser/domStylesheets.js';
import { createHotClass } from '../../../../../base/common/hotReloadHelpers.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import '../../../../../base/common/observableInternal/index.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { observableCodeEditor } from '../../../../browser/observableCodeEditor.js';
import { LineRange } from '../../../../common/core/ranges/lineRange.js';
import { InlineCompletionsHintsWidget } from '../hintsWidget/inlineCompletionsHintsWidget.js';
import { convertItemsToStableObservables } from '../utils.js';
import { GhostTextView, GhostTextWidgetWarning } from './ghostText/ghostTextView.js';
import { InlineEditsGutterIndicator, InlineEditsGutterIndicatorData, InlineSuggestionGutterMenuData, SimpleInlineSuggestModel } from './inlineEdits/components/gutterIndicatorView.js';
import { InlineEditsOnboardingExperience } from './inlineEdits/inlineEditsNewUsers.js';
import { InlineEditTabAction, InlineCompletionViewKind } from './inlineEdits/inlineEditsViewInterface.js';
import { InlineEditsViewAndDiffProducer } from './inlineEdits/inlineEditsViewProducer.js';
import { derived, derivedDisposable } from '../../../../../base/common/observableInternal/observables/derived.js';
import { derivedObservableWithCache, mapObservableArrayCached } from '../../../../../base/common/observableInternal/utils/utils.js';
import { observableValue } from '../../../../../base/common/observableInternal/observables/observableValue.js';
import { constObservable } from '../../../../../base/common/observableInternal/observables/constObservable.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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); }
};
let InlineSuggestionsView = class InlineSuggestionsView extends Disposable {
static { this.hot = createHotClass(this); }
constructor(_editor, _model, _focusIsInMenu, _instantiationService) {
super();
this._editor = _editor;
this._model = _model;
this._focusIsInMenu = _focusIsInMenu;
this._instantiationService = _instantiationService;
this._ghostTexts = derived(this, (reader) => {
const model = this._model.read(reader);
return model?.ghostTexts.read(reader) ?? [];
});
this._inlineEdit = derived(this, reader => this._model.read(reader)?.inlineEditState.read(reader)?.inlineSuggestion);
this._everHadInlineEdit = derivedObservableWithCache(this, (reader, last) => last || !!this._inlineEdit.read(reader)
|| !!this._model.read(reader)?.inlineCompletionState.read(reader)?.inlineSuggestion?.showInlineEditMenu);
// To break a cyclic dependency
this._indicatorIsHoverVisible = observableValue(this, undefined);
this._showInlineEditCollapsed = derived(this, reader => {
const s = this._model.read(reader)?.showCollapsed.read(reader) ?? false;
return s && !this._indicatorIsHoverVisible.read(reader)?.read(reader);
});
this._inlineEditWidget = derivedDisposable(reader => {
if (!this._everHadInlineEdit.read(reader)) {
return undefined;
}
return this._instantiationService.createInstance(InlineEditsViewAndDiffProducer, this._editor, this._model, this._showInlineEditCollapsed);
});
this._gutterIndicatorState = derived(reader => {
const model = this._model.read(reader);
if (!model) {
return undefined;
}
const state = model.state.read(reader);
if (state?.kind === 'ghostText' && state.inlineSuggestion?.showInlineEditMenu) {
return {
displayRange: LineRange.ofLength(state.primaryGhostText.lineNumber, 1),
tabAction: derived(this, reader => this._editorObs.isFocused.read(reader) ? InlineEditTabAction.Accept : InlineEditTabAction.Inactive),
gutterIndicatorOffset: constObservable(getGhostTextTopOffset(state.inlineSuggestion, this._editor)),
inlineSuggestion: state.inlineSuggestion,
model,
};
}
else if (state?.kind === 'inlineEdit') {
const inlineEditWidget = this._inlineEditWidget.read(reader)?.view;
if (!inlineEditWidget) {
return undefined;
}
const displayRange = inlineEditWidget.displayRange.read(reader);
if (!displayRange) {
return undefined;
}
return {
displayRange,
tabAction: derived(reader => {
if (this._editorObs.isFocused.read(reader)) {
if (model.tabShouldJumpToInlineEdit.read(reader)) {
return InlineEditTabAction.Jump;
}
if (model.tabShouldAcceptInlineEdit.read(reader)) {
return InlineEditTabAction.Accept;
}
}
return InlineEditTabAction.Inactive;
}),
gutterIndicatorOffset: inlineEditWidget.gutterIndicatorOffset,
inlineSuggestion: state.inlineSuggestion,
model,
};
}
else {
return undefined;
}
});
this._stablizedGhostTexts = convertItemsToStableObservables(this._ghostTexts, this._store);
this._editorObs = observableCodeEditor(this._editor);
this._ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => store.add(this._createGhostText(ghostText))).recomputeInitiallyAndOnChange(this._store);
this._inlineEditWidget.recomputeInitiallyAndOnChange(this._store);
this._fontFamily = this._editorObs.getOption(71 /* EditorOption.inlineSuggest */).map(val => val.fontFamily);
this._register(createStyleSheetFromObservable(derived(reader => {
const fontFamily = this._fontFamily.read(reader);
return `
.monaco-editor .ghost-text-decoration,
.monaco-editor .ghost-text-decoration-preview,
.monaco-editor .ghost-text {
font-family: ${fontFamily};
}`;
})));
this._register(new InlineCompletionsHintsWidget(this._editor, this._model, this._instantiationService));
this._indicator = this._register(this._instantiationService.createInstance(InlineEditsGutterIndicator, this._editorObs, derived(reader => {
const s = this._gutterIndicatorState.read(reader);
if (!s) {
return undefined;
}
return new InlineEditsGutterIndicatorData(InlineSuggestionGutterMenuData.fromInlineSuggestion(s.inlineSuggestion), s.displayRange, SimpleInlineSuggestModel.fromInlineCompletionModel(s.model), s.inlineSuggestion.action?.kind === 'edit' ? s.inlineSuggestion.action.alternativeAction : undefined);
}), this._gutterIndicatorState.map((s, reader) => s?.tabAction?.read(reader) ?? InlineEditTabAction.Inactive), this._gutterIndicatorState.map((s, reader) => s?.gutterIndicatorOffset?.read(reader) ?? 0), this._inlineEditWidget.map((w, reader) => w?.view.inlineEditsIsHovered.read(reader) ?? false), this._focusIsInMenu));
this._indicatorIsHoverVisible.set(this._indicator.isHoverVisible, undefined);
derived(reader => {
const w = this._inlineEditWidget.read(reader);
if (!w) {
return undefined;
}
return reader.store.add(this._instantiationService.createInstance(InlineEditsOnboardingExperience, w._inlineEditModel, constObservable(this._indicator), w.view._inlineCollapsedView));
}).recomputeInitiallyAndOnChange(this._store);
}
_createGhostText(ghostText) {
return this._instantiationService.createInstance(GhostTextView, this._editor, derived(reader => {
const model = this._model.read(reader);
const inlineCompletion = model?.inlineCompletionState.read(reader)?.inlineSuggestion;
if (!model || !inlineCompletion) {
// editor.suggest.preview: true causes situations where we have ghost text, but no suggest preview.
return {
ghostText: ghostText.read(reader),
handleInlineCompletionShown: () => { },
warning: undefined,
};
}
return {
ghostText: ghostText.read(reader),
handleInlineCompletionShown: (viewData) => model.handleInlineSuggestionShown(inlineCompletion, InlineCompletionViewKind.GhostText, viewData, Date.now()),
warning: GhostTextWidgetWarning.from(model?.warning.read(reader)),
};
}), {
useSyntaxHighlighting: this._editorObs.getOption(71 /* EditorOption.inlineSuggest */).map(v => v.syntaxHighlightingEnabled),
highlightShortSuggestions: true,
});
}
shouldShowHoverAtViewZone(viewZoneId) {
return this._ghostTextWidgets.get()[0]?.ownsViewZone(viewZoneId) ?? false;
}
};
InlineSuggestionsView = __decorate([
__param(3, IInstantiationService)
], InlineSuggestionsView);
function getGhostTextTopOffset(inlineCompletion, editor) {
const replacement = inlineCompletion.getSingleTextEdit();
const textModel = editor.getModel();
if (!textModel) {
return 0;
}
const EOL = textModel.getEOL();
if (replacement.range.isEmpty() && replacement.text.startsWith(EOL)) {
const lineHeight = editor.getLineHeightForPosition(replacement.range.getStartPosition());
return countPrefixRepeats(replacement.text, EOL) * lineHeight;
}
return 0;
}
function countPrefixRepeats(str, prefix) {
if (!prefix.length) {
return 0;
}
let count = 0;
let i = 0;
while (str.startsWith(prefix, i)) {
count++;
i += prefix.length;
}
return count;
}
export { InlineSuggestionsView };