@21epub/epub-thirdparty
Version:
epub-thirdparty
514 lines (513 loc) • 22.6 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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { createCancelablePromise, RunOnceScheduler } from '../../../base/common/async.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { onUnexpectedError, onUnexpectedExternalError } from '../../../base/common/errors.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable, MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { commonPrefixLength, commonSuffixLength } from '../../../base/common/strings.js';
import { CoreEditingCommands } from '../../browser/controller/coreCommands.js';
import { RedoCommand, UndoCommand } from '../../browser/editorExtensions.js';
import { EditOperation } from '../../common/core/editOperation.js';
import { Range } from '../../common/core/range.js';
import { InlineCompletionsProviderRegistry, InlineCompletionTriggerKind } from '../../common/modes.js';
import { BaseGhostTextWidgetModel } from './ghostText.js';
import { ICommandService } from '../../../platform/commands/common/commands.js';
import { inlineSuggestCommitId } from './consts.js';
import { inlineCompletionToGhostText } from './inlineCompletionToGhostText.js';
let InlineCompletionsModel = class InlineCompletionsModel extends Disposable {
constructor(editor, cache, commandService) {
super();
this.editor = editor;
this.cache = cache;
this.commandService = commandService;
this.onDidChangeEmitter = new Emitter();
this.onDidChange = this.onDidChangeEmitter.event;
this.completionSession = this._register(new MutableDisposable());
this.active = false;
this.disposed = false;
this._register(commandService.onDidExecuteCommand(e => {
// These commands don't trigger onDidType.
const commands = new Set([
UndoCommand.id,
RedoCommand.id,
CoreEditingCommands.Tab.id,
CoreEditingCommands.DeleteLeft.id,
CoreEditingCommands.DeleteRight.id,
inlineSuggestCommitId,
'acceptSelectedSuggestion'
]);
if (commands.has(e.commandId) && editor.hasTextFocus()) {
this.handleUserInput();
}
}));
this._register(this.editor.onDidType((e) => {
this.handleUserInput();
}));
this._register(this.editor.onDidChangeCursorPosition((e) => {
if (this.session && !this.session.isValid) {
this.hide();
}
}));
this._register(toDisposable(() => {
this.disposed = true;
}));
this._register(this.editor.onDidBlurEditorWidget(() => {
this.hide();
}));
}
handleUserInput() {
if (this.session && !this.session.isValid) {
this.hide();
}
setTimeout(() => {
if (this.disposed) {
return;
}
// Wait for the cursor update that happens in the same iteration loop iteration
this.startSessionIfTriggered();
}, 0);
}
get session() {
return this.completionSession.value;
}
get ghostText() {
var _a;
return (_a = this.session) === null || _a === void 0 ? void 0 : _a.ghostText;
}
get minReservedLineCount() {
return this.session ? this.session.minReservedLineCount : 0;
}
setExpanded(expanded) {
var _a;
(_a = this.session) === null || _a === void 0 ? void 0 : _a.setExpanded(expanded);
}
setActive(active) {
var _a;
this.active = active;
if (active) {
(_a = this.session) === null || _a === void 0 ? void 0 : _a.scheduleAutomaticUpdate();
}
}
startSessionIfTriggered() {
const suggestOptions = this.editor.getOption(54 /* inlineSuggest */);
if (!suggestOptions.enabled) {
return;
}
if (this.session && this.session.isValid) {
return;
}
this.trigger(InlineCompletionTriggerKind.Automatic);
}
trigger(triggerKind) {
if (this.completionSession.value) {
if (triggerKind === InlineCompletionTriggerKind.Explicit) {
void this.completionSession.value.ensureUpdateWithExplicitContext();
}
return;
}
this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active, this.commandService, this.cache, triggerKind);
this.completionSession.value.takeOwnership(this.completionSession.value.onDidChange(() => {
this.onDidChangeEmitter.fire();
}));
}
hide() {
this.completionSession.clear();
this.onDidChangeEmitter.fire();
}
commitCurrentSuggestion() {
var _a;
// Don't dispose the session, so that after committing, more suggestions are shown.
(_a = this.session) === null || _a === void 0 ? void 0 : _a.commitCurrentCompletion();
}
showNext() {
var _a;
(_a = this.session) === null || _a === void 0 ? void 0 : _a.showNextInlineCompletion();
}
showPrevious() {
var _a;
(_a = this.session) === null || _a === void 0 ? void 0 : _a.showPreviousInlineCompletion();
}
hasMultipleInlineCompletions() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const result = yield ((_a = this.session) === null || _a === void 0 ? void 0 : _a.hasMultipleInlineCompletions());
return result !== undefined ? result : false;
});
}
};
InlineCompletionsModel = __decorate([
__param(2, ICommandService)
], InlineCompletionsModel);
export { InlineCompletionsModel };
export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
constructor(editor, triggerPosition, shouldUpdate, commandService, cache, initialTriggerKind) {
super(editor);
this.triggerPosition = triggerPosition;
this.shouldUpdate = shouldUpdate;
this.commandService = commandService;
this.cache = cache;
this.initialTriggerKind = initialTriggerKind;
this.minReservedLineCount = 0;
this.updateOperation = this._register(new MutableDisposable());
this.updateSoon = this._register(new RunOnceScheduler(() => {
let triggerKind = this.initialTriggerKind;
// All subsequent triggers are automatic.
this.initialTriggerKind = InlineCompletionTriggerKind.Automatic;
return this.update(triggerKind);
}, 50));
//#region Selection
// We use a semantic id to track the selection even if the cache changes.
this.currentlySelectedCompletionId = undefined;
let lastCompletionItem = undefined;
this._register(this.onDidChange(() => {
const currentCompletion = this.currentCompletion;
if (currentCompletion && currentCompletion.sourceInlineCompletion !== lastCompletionItem) {
lastCompletionItem = currentCompletion.sourceInlineCompletion;
const provider = currentCompletion.sourceProvider;
if (provider.handleItemDidShow) {
provider.handleItemDidShow(currentCompletion.sourceInlineCompletions, lastCompletionItem);
}
}
}));
this._register(toDisposable(() => {
this.cache.clear();
}));
this._register(this.editor.onDidChangeCursorPosition((e) => {
if (this.cache.value) {
this.onDidChangeEmitter.fire();
}
}));
this._register(this.editor.onDidChangeModelContent((e) => {
this.scheduleAutomaticUpdate();
}));
this._register(InlineCompletionsProviderRegistry.onDidChange(() => {
this.updateSoon.schedule();
}));
this.scheduleAutomaticUpdate();
}
fixAndGetIndexOfCurrentSelection() {
if (!this.currentlySelectedCompletionId || !this.cache.value) {
return 0;
}
if (this.cache.value.completions.length === 0) {
// don't reset the selection in this case
return 0;
}
const idx = this.cache.value.completions.findIndex(v => v.semanticId === this.currentlySelectedCompletionId);
if (idx === -1) {
// Reset the selection so that the selection does not jump back when it appears again
this.currentlySelectedCompletionId = undefined;
return 0;
}
return idx;
}
get currentCachedCompletion() {
if (!this.cache.value) {
return undefined;
}
return this.cache.value.completions[this.fixAndGetIndexOfCurrentSelection()];
}
showNextInlineCompletion() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
yield this.ensureUpdateWithExplicitContext();
const completions = ((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions) || [];
if (completions.length > 0) {
const newIdx = (this.fixAndGetIndexOfCurrentSelection() + 1) % completions.length;
this.currentlySelectedCompletionId = completions[newIdx].semanticId;
}
else {
this.currentlySelectedCompletionId = undefined;
}
this.onDidChangeEmitter.fire();
});
}
showPreviousInlineCompletion() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
yield this.ensureUpdateWithExplicitContext();
const completions = ((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions) || [];
if (completions.length > 0) {
const newIdx = (this.fixAndGetIndexOfCurrentSelection() + completions.length - 1) % completions.length;
this.currentlySelectedCompletionId = completions[newIdx].semanticId;
}
else {
this.currentlySelectedCompletionId = undefined;
}
this.onDidChangeEmitter.fire();
});
}
ensureUpdateWithExplicitContext() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (this.updateOperation.value) {
// Restart or wait for current update operation
if (this.updateOperation.value.triggerKind === InlineCompletionTriggerKind.Explicit) {
yield this.updateOperation.value.promise;
}
else {
yield this.update(InlineCompletionTriggerKind.Explicit);
}
}
else if (((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.triggerKind) !== InlineCompletionTriggerKind.Explicit) {
// Refresh cache
yield this.update(InlineCompletionTriggerKind.Explicit);
}
});
}
hasMultipleInlineCompletions() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
yield this.ensureUpdateWithExplicitContext();
return (((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions.length) || 0) > 1;
});
}
//#endregion
get ghostText() {
const currentCompletion = this.currentCompletion;
const mode = this.editor.getOptions().get(54 /* inlineSuggest */).mode;
return currentCompletion ? inlineCompletionToGhostText(currentCompletion, this.editor.getModel(), mode, this.editor.getPosition()) : undefined;
}
get currentCompletion() {
const completion = this.currentCachedCompletion;
if (!completion) {
return undefined;
}
return completion.toLiveInlineCompletion();
}
get isValid() {
return this.editor.getPosition().lineNumber === this.triggerPosition.lineNumber;
}
scheduleAutomaticUpdate() {
// Since updateSoon debounces, starvation can happen.
// To prevent stale cache, we clear the current update operation.
this.updateOperation.clear();
this.updateSoon.schedule();
}
update(triggerKind) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.shouldUpdate()) {
return;
}
const position = this.editor.getPosition();
const promise = createCancelablePromise((token) => __awaiter(this, void 0, void 0, function* () {
let result;
try {
result = yield provideInlineCompletions(position, this.editor.getModel(), { triggerKind, selectedSuggestionInfo: undefined }, token);
}
catch (e) {
onUnexpectedError(e);
return;
}
if (token.isCancellationRequested) {
return;
}
this.cache.setValue(this.editor, result, triggerKind);
this.onDidChangeEmitter.fire();
}));
const operation = new UpdateOperation(promise, triggerKind);
this.updateOperation.value = operation;
yield promise;
if (this.updateOperation.value === operation) {
this.updateOperation.clear();
}
});
}
takeOwnership(disposable) {
this._register(disposable);
}
commitCurrentCompletion() {
if (!this.ghostText) {
// No ghost text was shown for this completion.
// Thus, we don't want to commit anything.
return;
}
const completion = this.currentCompletion;
if (completion) {
this.commit(completion);
}
}
commit(completion) {
// Mark the cache as stale, but don't dispose it yet,
// otherwise command args might get disposed.
const cache = this.cache.clearAndLeak();
this.editor.executeEdits('inlineSuggestion.accept', [
EditOperation.replaceMove(completion.range, completion.text)
]);
if (completion.command) {
this.commandService
.executeCommand(completion.command.id, ...(completion.command.arguments || []))
.finally(() => {
cache === null || cache === void 0 ? void 0 : cache.dispose();
})
.then(undefined, onUnexpectedExternalError);
}
else {
cache === null || cache === void 0 ? void 0 : cache.dispose();
}
this.onDidChangeEmitter.fire();
}
}
export class UpdateOperation {
constructor(promise, triggerKind) {
this.promise = promise;
this.triggerKind = triggerKind;
}
dispose() {
this.promise.cancel();
}
}
/**
* The cache keeps itself in sync with the editor.
* It also owns the completions result and disposes it when the cache is diposed.
*/
export class SynchronizedInlineCompletionsCache extends Disposable {
constructor(editor, completionsSource, onChange, triggerKind) {
super();
this.triggerKind = triggerKind;
const decorationIds = editor.deltaDecorations([], completionsSource.items.map(i => ({
range: i.range,
options: {
description: 'inline-completion-tracking-range'
},
})));
this._register(toDisposable(() => {
editor.deltaDecorations(decorationIds, []);
}));
this.completions = completionsSource.items.map((c, idx) => new CachedInlineCompletion(c, decorationIds[idx]));
this._register(editor.onDidChangeModelContent(() => {
let hasChanged = false;
const model = editor.getModel();
for (const c of this.completions) {
const newRange = model.getDecorationRange(c.decorationId);
if (!newRange) {
onUnexpectedError(new Error('Decoration has no range'));
continue;
}
if (!c.synchronizedRange.equalsRange(newRange)) {
hasChanged = true;
c.synchronizedRange = newRange;
}
}
if (hasChanged) {
onChange();
}
}));
this._register(completionsSource);
}
}
class CachedInlineCompletion {
constructor(inlineCompletion, decorationId) {
this.inlineCompletion = inlineCompletion;
this.decorationId = decorationId;
this.semanticId = JSON.stringify({
text: this.inlineCompletion.text,
startLine: this.inlineCompletion.range.startLineNumber,
startColumn: this.inlineCompletion.range.startColumn,
command: this.inlineCompletion.command
});
this.synchronizedRange = inlineCompletion.range;
}
toLiveInlineCompletion() {
return {
text: this.inlineCompletion.text,
range: this.synchronizedRange,
command: this.inlineCompletion.command,
sourceProvider: this.inlineCompletion.sourceProvider,
sourceInlineCompletions: this.inlineCompletion.sourceInlineCompletions,
sourceInlineCompletion: this.inlineCompletion.sourceInlineCompletion,
};
}
}
function getDefaultRange(position, model) {
const word = model.getWordAtPosition(position);
const maxColumn = model.getLineMaxColumn(position.lineNumber);
// By default, always replace up until the end of the current line.
// This default might be subject to change!
return word
? new Range(position.lineNumber, word.startColumn, position.lineNumber, maxColumn)
: Range.fromPositions(position, position.with(undefined, maxColumn));
}
export function provideInlineCompletions(position, model, context, token = CancellationToken.None) {
return __awaiter(this, void 0, void 0, function* () {
const defaultReplaceRange = getDefaultRange(position, model);
const providers = InlineCompletionsProviderRegistry.all(model);
const results = yield Promise.all(providers.map((provider) => __awaiter(this, void 0, void 0, function* () {
const completions = yield provider.provideInlineCompletions(model, position, context, token);
return ({
completions,
provider,
dispose: () => {
if (completions) {
provider.freeInlineCompletions(completions);
}
}
});
})));
const itemsByHash = new Map();
for (const result of results) {
const completions = result.completions;
if (completions) {
for (const item of completions.items.map(item => ({
text: item.text,
range: item.range ? Range.lift(item.range) : defaultReplaceRange,
command: item.command,
sourceProvider: result.provider,
sourceInlineCompletions: completions,
sourceInlineCompletion: item
}))) {
if (item.range.startLineNumber !== item.range.endLineNumber) {
// Ignore invalid ranges.
continue;
}
itemsByHash.set(JSON.stringify({ text: item.text, range: item.range }), item);
}
}
}
return {
items: [...itemsByHash.values()],
dispose: () => {
for (const result of results) {
result.dispose();
}
},
};
});
}
export function minimizeInlineCompletion(model, inlineCompletion) {
if (!inlineCompletion) {
return inlineCompletion;
}
const valueToReplace = model.getValueInRange(inlineCompletion.range);
const commonPrefixLen = commonPrefixLength(valueToReplace, inlineCompletion.text);
const startOffset = model.getOffsetAt(inlineCompletion.range.getStartPosition()) + commonPrefixLen;
const start = model.getPositionAt(startOffset);
const remainingValueToReplace = valueToReplace.substr(commonPrefixLen);
const commonSuffixLen = commonSuffixLength(remainingValueToReplace, inlineCompletion.text);
const end = model.getPositionAt(Math.max(startOffset, model.getOffsetAt(inlineCompletion.range.getEndPosition()) - commonSuffixLen));
return {
range: Range.fromPositions(start, end),
text: inlineCompletion.text.substr(commonPrefixLen, inlineCompletion.text.length - commonPrefixLen - commonSuffixLen),
};
}