/*---------------------------------------------------------------------------------------------
 *  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 './interactiveEditor.css';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js';
import { Range } from '../../../common/core/range.js';
import { localize } from '../../../../nls.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { ZoneWidget } from '../../zoneWidget/browser/zoneWidget.js';
import { assertType } from '../../../../base/common/types.js';
import { IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_PREVIEW, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE, CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE } from '../common/interactiveEditor.js';
import { EditOperation } from '../../../common/core/editOperation.js';
import { Iterable } from '../../../../base/common/iterator.js';
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
import { Dimension, addDisposableListener, getTotalHeight, getTotalWidth, h, reset } from '../../../../base/browser/dom.js';
import { Emitter } from '../../../../base/common/event.js';
import { CodeEditorWidget } from '../../../browser/widget/codeEditorWidget.js';
import { EditorExtensionsRegistry } from '../../../browser/editorExtensions.js';
import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
import { IModelService } from '../../../common/services/model.js';
import { URI } from '../../../../base/common/uri.js';
import { EmbeddedCodeEditorWidget } from '../../../browser/widget/embeddedCodeEditorWidget.js';
import { GhostTextController } from '../../inlineCompletions/browser/ghostTextController.js';
import { MenuWorkbenchToolBar, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js';
import { SuggestController } from '../../suggest/browser/suggestController.js';
import { Selection } from '../../../common/core/selection.js';
import { raceCancellationError } from '../../../../base/common/async.js';
import { isCancellationError } from '../../../../base/common/errors.js';
import { IEditorWorkerService } from '../../../common/services/editorWorker.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { StopWatch } from '../../../../base/common/stopwatch.js';
import { Action } from '../../../../base/common/actions.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { LRUCache } from '../../../../base/common/map.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IBulkEditService } from '../../../browser/services/bulkEditService.js';
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
let InteractiveEditorWidget = class InteractiveEditorWidget {
    constructor(parentEditor, _modelService, _contextKeyService, _instantiationService) {
        var _a;
        this._modelService = _modelService;
        this._contextKeyService = _contextKeyService;
        this._instantiationService = _instantiationService;
        this._elements = h('div.interactive-editor@root', [
            h('div.body', [
                h('div.content', [
                    h('div.input@input', [
                        h('div.editor-placeholder@placeholder'),
                        h('div.editor-container@editor'),
                    ]),
                    h('div.history.hidden@history'),
                ]),
                h('div.toolbar@rhsToolbar'),
            ]),
            h('div.progress@progress'),
            h('div.message.hidden@message'),
        ]);
        this._store = new DisposableStore();
        this._historyStore = new DisposableStore();
        this._onDidChangeHeight = new Emitter();
        this.onDidChangeHeight = this._onDidChangeHeight.event;
        this._isExpanded = false;
        this.acceptInput = InteractiveEditorWidget._noop;
        this._cancelInput = InteractiveEditorWidget._noop;
        this._ctxHistoryPossible = CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE.bindTo(this._contextKeyService);
        this._ctxHistoryVisible = CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE.bindTo(this._contextKeyService);
        // editor logic
        const editorOptions = {
            ariaLabel: localize('aria-label', "Interactive Editor Input"),
            wordWrap: 'on',
            overviewRulerLanes: 0,
            glyphMargin: false,
            lineNumbers: 'off',
            folding: false,
            selectOnLineNumbers: false,
            hideCursorInOverviewRuler: true,
            selectionHighlight: false,
            scrollbar: {
                useShadows: false,
                vertical: 'hidden',
                horizontal: 'auto',
                // alwaysConsumeMouseWheel: false
            },
            lineDecorationsWidth: 0,
            overviewRulerBorder: false,
            scrollBeyondLastLine: false,
            renderLineHighlight: 'none',
            fixedOverflowWidgets: true,
            dragAndDrop: false,
            revealHorizontalRightPadding: 5,
            minimap: { enabled: false },
            guides: { indentation: false },
            cursorWidth: 2,
            wrappingStrategy: 'advanced',
            wrappingIndent: 'none',
            padding: { top: 3, bottom: 2 },
            renderWhitespace: 'none',
            dropIntoEditor: { enabled: true },
            quickSuggestions: false,
            suggest: {
                showIcons: false,
                showSnippets: false,
            }
        };
        const codeEditorWidgetOptions = {
            isSimpleWidget: true,
            contributions: EditorExtensionsRegistry.getSomeEditorContributions([
                SnippetController2.ID,
                GhostTextController.ID,
                SuggestController.ID
            ])
        };
        this._inputEditor = parentEditor
            ? this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, editorOptions, codeEditorWidgetOptions, parentEditor)
            : this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, editorOptions, codeEditorWidgetOptions);
        this._store.add(this._inputEditor);
        const uri = URI.from({ scheme: 'vscode', authority: 'interactive-editor', path: `/interactive-editor/model${InteractiveEditorWidget._modelPool++}.txt` });
        this._inputModel = (_a = this._modelService.getModel(uri)) !== null && _a !== void 0 ? _a : this._modelService.createModel('', null, uri);
        this._inputEditor.setModel(this._inputModel);
        // show/hide placeholder depending on text model being empty
        // content height
        const currentContentHeight = 0;
        this._ctxInputEmpty = CTX_INTERACTIVE_EDITOR_EMPTY.bindTo(this._contextKeyService);
        const togglePlaceholder = () => {
            const hasText = this._inputModel.getValueLength() > 0;
            this._elements.placeholder.classList.toggle('hidden', hasText);
            this._ctxInputEmpty.set(!hasText);
            const contentHeight = this._inputEditor.getContentHeight();
            if (contentHeight !== currentContentHeight && this._editorDim) {
                this._editorDim = this._editorDim.with(undefined, contentHeight);
                this._inputEditor.layout(this._editorDim);
                this._onDidChangeHeight.fire();
            }
        };
        this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder));
        togglePlaceholder();
        this._store.add(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus()));
        const toolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.rhsToolbar, MENU_INTERACTIVE_EDITOR_WIDGET, {
            telemetrySource: 'interactiveEditorWidget-toolbar',
            toolbarOptions: { primaryGroup: 'main' }
        });
        this._store.add(toolbar);
        this._progressBar = new ProgressBar(this._elements.progress);
        this._store.add(this._progressBar);
    }
    dispose() {
        this._store.dispose();
        this._historyStore.dispose();
        this._ctxInputEmpty.reset();
        this._ctxHistoryVisible.reset();
    }
    get domNode() {
        return this._elements.root;
    }
    layout(dim) {
        const innerEditorWidth = Math.min(Number.MAX_SAFE_INTEGER, //  TODO@jrieken define max width?
        dim.width - (getTotalWidth(this._elements.rhsToolbar) + 12 /* L/R-padding */));
        const newDim = new Dimension(innerEditorWidth, this._inputEditor.getContentHeight());
        if (!this._editorDim || !Dimension.equals(this._editorDim, newDim)) {
            this._editorDim = newDim;
            this._inputEditor.layout(this._editorDim);
            this._elements.placeholder.style.width = `${innerEditorWidth - 4 /* input-padding*/}px`;
        }
    }
    getHeight() {
        const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.message) + getTotalHeight(this._elements.history);
        const editorHeight = this._inputEditor.getContentHeight() + 6 /* padding and border */;
        return base + editorHeight + 12 /* padding */;
    }
    updateProgress(show) {
        if (show) {
            this._progressBar.infinite();
        }
        else {
            this._progressBar.stop();
        }
    }
    getInput(placeholder, value, token) {
        this._elements.placeholder.innerText = placeholder;
        this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(50 /* EditorOption.fontSize */)}px`;
        this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(64 /* EditorOption.lineHeight */)}px`;
        this._inputModel.setValue(value);
        this._inputEditor.setSelection(this._inputModel.getFullModelRange());
        const disposeOnDone = new DisposableStore();
        disposeOnDone.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire()));
        const ctxInnerCursorFirst = CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST.bindTo(this._contextKeyService);
        const ctxInnerCursorLast = CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST.bindTo(this._contextKeyService);
        const ctxInputEditorFocused = CTX_INTERACTIVE_EDITOR_FOCUSED.bindTo(this._contextKeyService);
        return new Promise(resolve => {
            this._cancelInput = () => {
                this.acceptInput = InteractiveEditorWidget._noop;
                this._cancelInput = InteractiveEditorWidget._noop;
                resolve(undefined);
                return true;
            };
            this.acceptInput = (preview) => {
                const newValue = this._inputEditor.getModel().getValue();
                if (newValue.trim().length === 0) {
                    // empty or whitespace only
                    this._cancelInput();
                    return;
                }
                this.acceptInput = InteractiveEditorWidget._noop;
                this._cancelInput = InteractiveEditorWidget._noop;
                resolve({ value: newValue, preview });
            };
            disposeOnDone.add(token.onCancellationRequested(() => this._cancelInput()));
            // CONTEXT KEYS
            // (1) inner cursor position (last/first line selected)
            const updateInnerCursorFirstLast = () => {
                if (!this._inputEditor.hasModel()) {
                    return;
                }
                const { lineNumber } = this._inputEditor.getPosition();
                ctxInnerCursorFirst.set(lineNumber === 1);
                ctxInnerCursorLast.set(lineNumber === this._inputEditor.getModel().getLineCount());
            };
            disposeOnDone.add(this._inputEditor.onDidChangeCursorPosition(updateInnerCursorFirstLast));
            updateInnerCursorFirstLast();
            // (2) input editor focused or not
            const updateFocused = () => {
                const hasFocus = this._inputEditor.hasWidgetFocus();
                ctxInputEditorFocused.set(hasFocus);
                this._elements.input.classList.toggle('synthetic-focus', hasFocus);
            };
            disposeOnDone.add(this._inputEditor.onDidFocusEditorWidget(updateFocused));
            disposeOnDone.add(this._inputEditor.onDidBlurEditorWidget(updateFocused));
            updateFocused();
            this.focus();
        }).finally(() => {
            disposeOnDone.dispose();
            ctxInnerCursorFirst.reset();
            ctxInnerCursorLast.reset();
            ctxInputEditorFocused.reset();
        });
    }
    populateInputField(value) {
        this._inputModel.setValue(value.trim());
        this._inputEditor.setSelection(this._inputModel.getFullModelRange());
    }
    toggleHistory() {
        this._isExpanded = !this._isExpanded;
        this._elements.history.classList.toggle('hidden', !this._isExpanded);
        this._ctxHistoryVisible.set(this._isExpanded);
        this._onDidChangeHeight.fire();
    }
    createHistoryEntry(value) {
        const { root, label, actions } = h('div.history-entry@item', [
            h('div.label@label'),
            h('div.actions@actions'),
        ]);
        label.innerText = value;
        const toolbar = this._instantiationService.createInstance(WorkbenchToolBar, actions, {});
        this._historyStore.add(toolbar);
        this._elements.history.insertBefore(root, this._elements.history.firstChild);
        if (this._isExpanded) {
            this._onDidChangeHeight.fire();
        }
        this._ctxHistoryPossible.set(true);
        return {
            updateVisibility: (visible) => {
                root.classList.toggle('hidden', !visible);
                if (this._isExpanded) {
                    this._onDidChangeHeight.fire();
                }
            },
            updateActions(actions) {
                toolbar.setActions(actions);
            },
            remove: () => {
                root.remove();
                if (this._isExpanded) {
                    this._onDidChangeHeight.fire();
                }
            }
        };
    }
    clearHistory() {
        this._historyStore.clear();
        this._isExpanded = false;
        this._elements.history.classList.toggle('hidden', true);
        this._ctxHistoryPossible.reset();
        this._ctxHistoryVisible.reset();
        reset(this._elements.history);
    }
    showMessage(value) {
        this._elements.message.classList.remove('hidden');
        this._elements.message.innerText = value;
        this._onDidChangeHeight.fire();
    }
    clearMessage() {
        this._elements.message.classList.add('hidden');
        reset(this._elements.message);
        this._onDidChangeHeight.fire();
    }
    reset() {
        this._ctxInputEmpty.reset();
        this.clearHistory();
        this.clearMessage();
    }
    focus() {
        this._inputEditor.focus();
    }
};
InteractiveEditorWidget._modelPool = 1;
InteractiveEditorWidget._noop = () => { };
InteractiveEditorWidget = __decorate([
    __param(1, IModelService),
    __param(2, IContextKeyService),
    __param(3, IInstantiationService)
], InteractiveEditorWidget);
let InteractiveEditorZoneWidget = class InteractiveEditorZoneWidget extends ZoneWidget {
    constructor(editor, _instaService, contextKeyService) {
        super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'interactive-editor-widget', keepEditorSelection: true });
        this._instaService = _instaService;
        this._ctxVisible = CTX_INTERACTIVE_EDITOR_VISIBLE.bindTo(contextKeyService);
        this._ctxCursorPosition = CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.bindTo(contextKeyService);
        this._disposables.add(toDisposable(() => {
            this._ctxVisible.reset();
            this._ctxCursorPosition.reset();
        }));
        this.widget = this._instaService.createInstance(InteractiveEditorWidget, this.editor);
        this._disposables.add(this.widget.onDidChangeHeight(() => this._relayout()));
        this._disposables.add(this.widget);
        this.create();
        // todo@jrieken listen ONLY when showing
        const updateCursorIsAboveContextKey = () => {
            if (!this.position || !this.editor.hasModel()) {
                this._ctxCursorPosition.reset();
            }
            else if (this.position.lineNumber === this.editor.getPosition().lineNumber) {
                this._ctxCursorPosition.set('above');
            }
            else if (this.position.lineNumber + 1 === this.editor.getPosition().lineNumber) {
                this._ctxCursorPosition.set('below');
            }
            else {
                this._ctxCursorPosition.reset();
            }
        };
        this._disposables.add(this.editor.onDidChangeCursorPosition(e => updateCursorIsAboveContextKey()));
        this._disposables.add(this.editor.onDidFocusEditorText(e => updateCursorIsAboveContextKey()));
        updateCursorIsAboveContextKey();
    }
    _fillContainer(container) {
        container.appendChild(this.widget.domNode);
    }
    _getWidth(info) {
        // TODO@jrieken
        // makes the zone widget wider than wanted but this aligns
        // it with wholeLine decorations that are added above
        return info.width;
    }
    _onWidth(widthInPixel) {
        if (this._dimension) {
            this._doLayout(this._dimension.height, widthInPixel);
        }
    }
    _doLayout(heightInPixel, widthInPixel) {
        const info = this.editor.getLayoutInfo();
        const spaceLeft = info.lineNumbersWidth + info.glyphMarginWidth + info.decorationsWidth;
        const spaceRight = info.minimap.minimapWidth + info.verticalScrollbarWidth;
        const width = widthInPixel - (spaceLeft + spaceRight);
        this._dimension = new Dimension(width, heightInPixel);
        this.widget.domNode.style.marginLeft = `${spaceLeft}px`;
        this.widget.domNode.style.marginRight = `${spaceRight}px`;
        this.widget.layout(this._dimension);
    }
    _computeHeightInLines() {
        const lineHeight = this.editor.getOption(64 /* EditorOption.lineHeight */);
        return this.widget.getHeight() / lineHeight;
    }
    _relayout() {
        super._relayout(this._computeHeightInLines());
    }
    getInput(where, placeholder, value, token) {
        const _super = Object.create(null, {
            show: { get: () => super.show }
        });
        return __awaiter(this, void 0, void 0, function* () {
            assertType(this.editor.hasModel());
            _super.show.call(this, where, this._computeHeightInLines());
            this._ctxVisible.set(true);
            const task = this.widget.getInput(placeholder, value, token);
            const result = yield task;
            return result;
        });
    }
    updatePosition(where) {
        super.show(where, this._computeHeightInLines());
    }
    hide() {
        this._ctxVisible.reset();
        this._ctxCursorPosition.reset();
        this.widget.reset();
        super.hide();
    }
};
InteractiveEditorZoneWidget = __decorate([
    __param(1, IInstantiationService),
    __param(2, IContextKeyService)
], InteractiveEditorZoneWidget);
export { InteractiveEditorZoneWidget };
class UndoStepAction extends Action {
    static updateUndoSteps() {
        UndoStepAction.all.forEach(action => {
            const isMyAltId = action.myAlternativeVersionId === action.model.getAlternativeVersionId();
            action.enabled = isMyAltId;
        });
    }
    constructor(model) {
        super(`undo@${model.getAlternativeVersionId()}`, localize('undoStep', "Undo This Step"), ThemeIcon.asClassName(Codicon.discard), false);
        this.model = model;
        this.myAlternativeVersionId = model.getAlternativeVersionId();
        UndoStepAction.all.push(this);
        UndoStepAction.updateUndoSteps();
    }
    run() {
        return __awaiter(this, void 0, void 0, function* () {
            this.model.undo();
            UndoStepAction.updateUndoSteps();
        });
    }
}
UndoStepAction.all = [];
class SessionRecorder {
    constructor() {
        this._data = new LRUCache(3);
    }
    add(session, model) {
        this._data.set(session, { when: new Date(), session, value: model.getValue(), exchanges: [] });
    }
    addExchange(session, req, res) {
        var _a;
        (_a = this._data.get(session)) === null || _a === void 0 ? void 0 : _a.exchanges.push({ req, res });
    }
    getAll() {
        return [...this._data.values()];
    }
}
let InteractiveEditorController = class InteractiveEditorController {
    static get(editor) {
        return editor.getContribution(InteractiveEditorController.ID);
    }
    constructor(_editor, instaService, contextKeyService, _interactiveEditorService, _bulkEditService, _editorWorkerService, _logService, _telemetryService) {
        this._editor = _editor;
        this._interactiveEditorService = _interactiveEditorService;
        this._bulkEditService = _bulkEditService;
        this._editorWorkerService = _editorWorkerService;
        this._logService = _logService;
        this._telemetryService = _telemetryService;
        this._historyOffset = -1;
        this._store = new DisposableStore();
        this._recorder = new SessionRecorder();
        this._ctsSession = new CancellationTokenSource();
        this._preview = false; // TODO@jrieken persist this
        this._zone = this._store.add(instaService.createInstance(InteractiveEditorZoneWidget, this._editor));
        this._ctxShowPreview = CTX_INTERACTIVE_EDITOR_PREVIEW.bindTo(contextKeyService);
        this._ctxHasActiveRequest = CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST.bindTo(contextKeyService);
    }
    dispose() {
        this._store.dispose();
        this._ctsSession.dispose(true);
        this._ctsSession.dispose();
    }
    run(initialRange) {
        var _a, _b, _c, _d, _e;
        return __awaiter(this, void 0, void 0, function* () {
            this._ctsSession.dispose(true);
            if (!this._editor.hasModel()) {
                return;
            }
            const provider = Iterable.first(this._interactiveEditorService.getAll());
            if (!provider) {
                this._logService.trace('[IE] NO provider found');
                return;
            }
            const thisSession = this._ctsSession = new CancellationTokenSource();
            const textModel = this._editor.getModel();
            const session = yield provider.prepareInteractiveEditorSession(textModel, this._editor.getSelection(), this._ctsSession.token);
            if (!session) {
                this._logService.trace('[IE] NO session', provider.debugName);
                return;
            }
            this._recorder.add(session, textModel);
            this._logService.trace('[IE] NEW session', provider.debugName);
            const data = {
                extension: provider.debugName,
                startTime: new Date().toISOString(),
                endTime: new Date().toISOString(),
                edits: false,
                terminalEdits: false,
                rounds: '',
                undos: ''
            };
            const blockDecoration = this._editor.createDecorationsCollection();
            const inlineDiffDecorations = this._editor.createDecorationsCollection();
            const wholeRangeDecoration = this._editor.createDecorationsCollection();
            if (!initialRange) {
                initialRange = this._editor.getSelection();
            }
            if (initialRange.isEmpty()) {
                initialRange = new Range(initialRange.startLineNumber, 1, initialRange.startLineNumber, textModel.getLineMaxColumn(initialRange.startLineNumber));
            }
            wholeRangeDecoration.set([{
                    range: initialRange,
                    options: { description: 'interactive-editor-marker' }
                }]);
            let placeholder = (_a = session.placeholder) !== null && _a !== void 0 ? _a : '';
            let value = '';
            const listener = new DisposableStore();
            // CANCEL when input changes
            this._editor.onDidChangeModel(this._ctsSession.cancel, this._ctsSession, listener);
            // REposition the zone widget whenever the block decoration changes
            let lastPost;
            wholeRangeDecoration.onDidChange(e => {
                const range = wholeRangeDecoration.getRange(0);
                if (range && (!lastPost || !lastPost.equals(range.getEndPosition()))) {
                    lastPost = range.getEndPosition();
                    this._zone.updatePosition(lastPost);
                }
            }, undefined, listener);
            let ignoreModelChanges = false;
            this._editor.onDidChangeModelContent(e => {
                // UPDATE undo actions based on alternative version id
                UndoStepAction.updateUndoSteps();
                data.edits = true;
                // CANCEL if the document has changed outside the current range
                if (!ignoreModelChanges) {
                    const wholeRange = wholeRangeDecoration.getRange(0);
                    if (!wholeRange) {
                        this._ctsSession.cancel();
                        this._logService.trace('[IE] ABORT wholeRange seems gone/collapsed');
                        return;
                    }
                    for (const change of e.changes) {
                        if (!Range.areIntersectingOrTouching(wholeRange, change.range)) {
                            this._ctsSession.cancel();
                            this._logService.trace('[IE] CANCEL because of model change OUTSIDE range');
                            data.terminalEdits = true;
                            break;
                        }
                    }
                }
            }, undefined, listener);
            let round = 0;
            do {
                round += 1;
                const wholeRange = wholeRangeDecoration.getRange(0);
                if (!wholeRange) {
                    // nuked whole file contents?
                    this._logService.trace('[IE] ABORT wholeRange seems gone/collapsed');
                    break;
                }
                // visuals: add block decoration
                blockDecoration.set([{
                        range: wholeRange,
                        options: InteractiveEditorController._decoBlock
                    }]);
                (_b = this._ctsRequest) === null || _b === void 0 ? void 0 : _b.dispose(true);
                this._ctsRequest = new CancellationTokenSource(this._ctsSession.token);
                this._historyOffset = -1;
                const input = yield this._zone.getInput(wholeRange.getEndPosition(), placeholder, value, this._ctsRequest.token);
                this._zone.widget.clearMessage();
                if (!input || !input.value) {
                    continue;
                }
                const historyEntry = this._zone.widget.createHistoryEntry(input.value);
                const sw = StopWatch.create();
                const request = {
                    prompt: input.value,
                    selection: this._editor.getSelection(),
                    wholeRange
                };
                const task = provider.provideResponse(session, request, this._ctsRequest.token);
                this._logService.trace('[IE] request started', provider.debugName, session, request);
                let reply;
                try {
                    this._zone.widget.updateProgress(true);
                    this._ctxHasActiveRequest.set(true);
                    reply = yield raceCancellationError(Promise.resolve(task), this._ctsRequest.token);
                }
                catch (e) {
                    if (!isCancellationError(e)) {
                        this._logService.error('[IE] ERROR during request', provider.debugName);
                        this._logService.error(e);
                        this._zone.widget.showMessage(toErrorMessage(e));
                        continue;
                    }
                }
                finally {
                    this._ctxHasActiveRequest.set(false);
                    this._zone.widget.updateProgress(false);
                    this._logService.trace('[IE] request took', sw.elapsed(), provider.debugName);
                }
                if (this._ctsRequest.token.isCancellationRequested) {
                    this._logService.trace('[IE] request CANCELED', provider.debugName);
                    value = input.value;
                    historyEntry.remove();
                    continue;
                }
                if (!reply) {
                    this._logService.trace('[IE] NO reply or edits', provider.debugName);
                    value = input.value;
                    this._zone.widget.showMessage(localize('empty', "No results, tweak your input and try again."));
                    historyEntry.remove();
                    continue;
                }
                if (reply.type === 'bulkEdit') {
                    this._logService.info('[IE] performaing a BULK EDIT, exiting interactive editor', provider.debugName);
                    this._bulkEditService.apply(reply.edits, { editor: this._editor, label: localize('ie', "{0}", input.value) });
                    // todo@jrieken preview bulk edit?
                    // todo@jrieken keep interactive editor?
                    break;
                }
                if (reply.type === 'message') {
                    this._logService.info('[IE] received a MESSAGE, exiting interactive editor', provider.debugName);
                    // todo@jrieken show message in context
                    // todo@jrieken keep interactive editor?
                    break;
                }
                // make edits more minimal
                const moreMinimalEdits = (yield this._editorWorkerService.computeMoreMinimalEdits(textModel.uri, reply.edits, true));
                this._logService.trace('[IE] edits from PROVIDER and after making them MORE MINIMAL', provider.debugName, reply.edits, moreMinimalEdits);
                this._recorder.addExchange(session, request, reply);
                // inline diff
                inlineDiffDecorations.clear();
                const newInlineDiffDecorationsData = [];
                try {
                    ignoreModelChanges = true;
                    const cursorStateComputerAndInlineDiffCollection = (undoEdits) => {
                        let last = null;
                        for (const edit of undoEdits) {
                            last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last;
                            newInlineDiffDecorationsData.push(InteractiveEditorController._asInlineDiffDecorationData(edit));
                        }
                        return last && [Selection.fromPositions(last)];
                    };
                    this._editor.pushUndoStop();
                    this._editor.executeEdits('interactive-editor', (moreMinimalEdits !== null && moreMinimalEdits !== void 0 ? moreMinimalEdits : reply.edits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)), cursorStateComputerAndInlineDiffCollection);
                    this._editor.pushUndoStop();
                }
                finally {
                    ignoreModelChanges = false;
                }
                inlineDiffDecorations.set(input.preview ? newInlineDiffDecorationsData : []);
                const that = this;
                historyEntry.updateActions([new class extends UndoStepAction {
                        constructor() {
                            super(textModel);
                        }
                        run() {
                            const _super = Object.create(null, {
                                run: { get: () => super.run }
                            });
                            var _a;
                            return __awaiter(this, void 0, void 0, function* () {
                                _super.run.call(this);
                                historyEntry.updateVisibility(false);
                                value = input.value;
                                (_a = that._ctsRequest) === null || _a === void 0 ? void 0 : _a.cancel();
                                data.undos += round + '|';
                            });
                        }
                    }]);
                if (!InteractiveEditorController._promptHistory.includes(input.value)) {
                    InteractiveEditorController._promptHistory.unshift(input.value);
                }
                placeholder = (_d = (_c = reply.placeholder) !== null && _c !== void 0 ? _c : session.placeholder) !== null && _d !== void 0 ? _d : '';
                value = '';
                data.rounds += round + '|';
            } while (!thisSession.token.isCancellationRequested);
            // done, cleanup
            wholeRangeDecoration.clear();
            blockDecoration.clear();
            inlineDiffDecorations.clear();
            listener.dispose();
            (_e = session.dispose) === null || _e === void 0 ? void 0 : _e.call(session);
            dispose(UndoStepAction.all);
            this._zone.hide();
            this._editor.focus();
            this._logService.trace('[IE] session DONE', provider.debugName);
            data.endTime = new Date().toISOString();
            this._telemetryService.publicLog2('interactiveEditor/session', data);
        });
    }
    static _asInlineDiffDecorationData(edit) {
        let content = edit.text;
        if (content.length > 12) {
            content = content.substring(0, 12) + '…';
        }
        return {
            range: edit.range,
            options: {
                description: 'interactive-editor-inline-diff',
                className: 'interactive-editor-lines-inserted-range',
                before: {
                    content,
                    inlineClassName: 'interactive-editor-lines-deleted-range-inline',
                    attachedData: edit
                }
            }
        };
    }
    accept(preview = this._preview) {
        this._zone.widget.acceptInput(preview);
    }
    togglePreview() {
        this._preview = !this._preview;
        this._ctxShowPreview.set(this._preview);
    }
    cancelCurrentRequest() {
        var _a;
        (_a = this._ctsRequest) === null || _a === void 0 ? void 0 : _a.cancel();
    }
    cancelSession() {
        this._ctsSession.cancel();
    }
    arrowOut(up) {
        if (this._zone.position && this._editor.hasModel()) {
            const { column } = this._editor.getPosition();
            const { lineNumber } = this._zone.position;
            const newLine = up ? lineNumber : lineNumber + 1;
            this._editor.setPosition({ lineNumber: newLine, column });
            this._editor.focus();
        }
    }
    focus() {
        this._zone.widget.focus();
    }
    populateHistory(up) {
        const len = InteractiveEditorController._promptHistory.length;
        if (len === 0) {
            return;
        }
        const pos = (len + this._historyOffset + (up ? 1 : -1)) % len;
        const entry = InteractiveEditorController._promptHistory[pos];
        this._zone.widget.populateInputField(entry);
        this._historyOffset = pos;
    }
    toggleHistory() {
        this._zone.widget.toggleHistory();
    }
    recordings() {
        return this._recorder.getAll();
    }
};
InteractiveEditorController.ID = 'interactiveEditor';
InteractiveEditorController._decoBlock = ModelDecorationOptions.register({
    description: 'interactive-editor',
    blockClassName: 'interactive-editor-block',
    blockDoesNotCollapse: true,
    blockPadding: [4, 0, 1, 4]
});
InteractiveEditorController._promptHistory = [];
InteractiveEditorController = __decorate([
    __param(1, IInstantiationService),
    __param(2, IContextKeyService),
    __param(3, IInteractiveEditorService),
    __param(4, IBulkEditService),
    __param(5, IEditorWorkerService),
    __param(6, ILogService),
    __param(7, ITelemetryService)
], InteractiveEditorController);
export { InteractiveEditorController };
