/*---------------------------------------------------------------------------------------------
 *  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());
    });
};
var _a;
import { Disposable, DisposableStore, dispose } from '../../../../base/common/lifecycle.js';
import { registerEditorContribution } from '../../../browser/editorExtensions.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { OutlineModel, OutlineElement } from '../../documentSymbols/browser/outlineModel.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import * as dom from '../../../../base/browser/dom.js';
import { createStringBuilder } from '../../../common/core/stringBuilder.js';
import { RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import { Position } from '../../../common/core/position.js';
import './stickyScroll.css';
let StickyScrollController = class StickyScrollController extends Disposable {
    constructor(editor, _languageFeaturesService) {
        super();
        this._sessionStore = new DisposableStore();
        this._ranges = [];
        this._rangesVersionId = 0;
        this._editor = editor;
        this._languageFeaturesService = _languageFeaturesService;
        this.stickyScrollWidget = new StickyScrollWidget(this._editor);
        this._register(this._editor.onDidChangeConfiguration(e => {
            if (e.hasChanged(34 /* EditorOption.experimental */)) {
                this.onConfigurationChange();
            }
        }));
        this._updateSoon = this._register(new RunOnceScheduler(() => this._update(true), 50));
        this.onConfigurationChange();
    }
    onConfigurationChange() {
        const options = this._editor.getOption(34 /* EditorOption.experimental */);
        if (options.stickyScroll.enabled === false) {
            this.stickyScrollWidget.emptyRootNode();
            this._editor.removeOverlayWidget(this.stickyScrollWidget);
            this._sessionStore.clear();
            return;
        }
        else {
            this._editor.addOverlayWidget(this.stickyScrollWidget);
            this._sessionStore.add(this._editor.onDidChangeModel(() => this._update(true)));
            this._sessionStore.add(this._editor.onDidScrollChange(() => this._update(false)));
            this._sessionStore.add(this._editor.onDidChangeHiddenAreas(() => this._update(true)));
            this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e)));
            this._sessionStore.add(this._editor.onDidLayoutChange(() => this._onDidResize()));
            this._sessionStore.add(this._editor.onDidChangeModelContent(() => this._updateSoon.schedule()));
            this._sessionStore.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => this._update(true)));
            const lineNumberOption = this._editor.getOption(62 /* EditorOption.lineNumbers */);
            if (lineNumberOption.renderType === 2 /* RenderLineNumbersType.Relative */) {
                this._sessionStore.add(this._editor.onDidChangeCursorPosition(() => this._update(false)));
            }
            this._update(true);
        }
    }
    _onDidResize() {
        const width = this._editor.getLayoutInfo().width - this._editor.getLayoutInfo().minimap.minimapCanvasOuterWidth - this._editor.getLayoutInfo().verticalScrollbarWidth;
        this.stickyScrollWidget.getDomNode().style.width = `${width}px`;
    }
    _needsUpdate(event) {
        const stickyLineNumbers = this.stickyScrollWidget.getCurrentLines();
        for (const stickyLineNumber of stickyLineNumbers) {
            for (const range of event.ranges) {
                if (stickyLineNumber >= range.fromLineNumber && stickyLineNumber <= range.toLineNumber) {
                    return true;
                }
            }
        }
        return false;
    }
    _onTokensChange(event) {
        if (this._needsUpdate(event)) {
            this._update(false);
        }
    }
    _update(updateOutline = false) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            if (updateOutline) {
                (_a = this._cts) === null || _a === void 0 ? void 0 : _a.dispose(true);
                this._cts = new CancellationTokenSource();
                yield this._updateOutlineModel(this._cts.token);
            }
            const hiddenRanges = (_b = this._editor._getViewModel()) === null || _b === void 0 ? void 0 : _b.getHiddenAreas();
            if (hiddenRanges) {
                for (const hiddenRange of hiddenRanges) {
                    this._ranges = this._ranges.filter(range => { return !(range[0] >= hiddenRange.startLineNumber && range[1] <= hiddenRange.endLineNumber + 1); });
                }
            }
            this._renderStickyScroll();
        });
    }
    _findLineRanges(outlineElement, depth) {
        if (outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.children.size) {
            let didRecursion = false;
            for (const outline of outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.children.values()) {
                const kind = outline.symbol.kind;
                if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */ || kind === 1 /* SymbolKind.Module */) {
                    didRecursion = true;
                    this._findLineRanges(outline, depth + 1);
                }
            }
            if (!didRecursion) {
                this._addOutlineRanges(outlineElement, depth);
            }
        }
        else {
            this._addOutlineRanges(outlineElement, depth);
        }
    }
    _addOutlineRanges(outlineElement, depth) {
        let currentStartLine = 0;
        let currentEndLine = 0;
        while (outlineElement) {
            const kind = outlineElement.symbol.kind;
            if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */ || kind === 1 /* SymbolKind.Module */) {
                currentStartLine = outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.symbol.range.startLineNumber;
                currentEndLine = outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.symbol.range.endLineNumber;
                this._ranges.push([currentStartLine, currentEndLine, depth]);
                depth--;
            }
            if (outlineElement.parent instanceof OutlineElement) {
                outlineElement = outlineElement.parent;
            }
            else {
                break;
            }
        }
    }
    _updateOutlineModel(token) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._editor.hasModel()) {
                const model = this._editor.getModel();
                const modelVersionId = model.getVersionId();
                const outlineModel = yield OutlineModel.create(this._languageFeaturesService.documentSymbolProvider, model, token);
                if (token.isCancellationRequested) {
                    return;
                }
                this._ranges = [];
                this._rangesVersionId = modelVersionId;
                for (const outline of outlineModel.children.values()) {
                    if (outline instanceof OutlineElement) {
                        const kind = outline.symbol.kind;
                        if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */ || kind === 1 /* SymbolKind.Module */) {
                            this._findLineRanges(outline, 1);
                        }
                        else {
                            this._findLineRanges(outline, 0);
                        }
                    }
                    this._ranges.sort(function (a, b) {
                        if (a[0] !== b[0]) {
                            return a[0] - b[0];
                        }
                        else if (a[1] !== b[1]) {
                            return b[1] - a[1];
                        }
                        else {
                            return a[2] - b[2];
                        }
                    });
                    const startLinesConsidered = new Set();
                    this._ranges = this._ranges.filter(arr => {
                        if (!this._containsArray(startLinesConsidered, arr)) {
                            startLinesConsidered.add(arr);
                            return true;
                        }
                        else {
                            return false;
                        }
                    });
                }
            }
        });
    }
    _containsArray(set, array) {
        for (const arr of set) {
            if (arr.toString() === array.toString()) {
                return true;
            }
        }
        return false;
    }
    _renderStickyScroll() {
        if (!(this._editor.hasModel())) {
            return;
        }
        const lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */);
        const model = this._editor.getModel();
        if (this._rangesVersionId !== model.getVersionId()) {
            // Old _ranges not updated yet
            return;
        }
        const scrollTop = this._editor.getScrollTop();
        this.stickyScrollWidget.emptyRootNode();
        for (const arr of this._ranges) {
            const [start, end, depth] = arr;
            if (end - start > 0) {
                const topOfElementAtDepth = (depth - 1) * lineHeight;
                const bottomOfElementAtDepth = depth * lineHeight;
                const bottomOfBeginningLine = this._editor.getBottomForLineNumber(start) - scrollTop;
                const topOfEndLine = this._editor.getTopForLineNumber(end) - scrollTop;
                const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop;
                if (topOfElementAtDepth >= topOfEndLine - 1 && topOfElementAtDepth < bottomOfEndLine - 2) {
                    this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(start, depth, this._editor, -1, bottomOfEndLine - bottomOfElementAtDepth));
                    break;
                }
                else if (bottomOfElementAtDepth > bottomOfBeginningLine && bottomOfElementAtDepth < bottomOfEndLine - 1) {
                    this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(start, depth, this._editor, 0, 0));
                }
            }
        }
        this.stickyScrollWidget.updateRootNode();
    }
    dispose() {
        super.dispose();
        this._sessionStore.dispose();
    }
};
StickyScrollController.ID = 'store.contrib.stickyScrollController';
StickyScrollController = __decorate([
    __param(1, ILanguageFeaturesService)
], StickyScrollController);
const _ttPolicy = (_a = window.trustedTypes) === null || _a === void 0 ? void 0 : _a.createPolicy('stickyScrollViewLayer', { createHTML: value => value });
class StickyScrollCodeLine extends Disposable {
    constructor(_lineNumber, _depth, _editor, _zIndex, _relativePosition) {
        super();
        this._lineNumber = _lineNumber;
        this._depth = _depth;
        this._editor = _editor;
        this._zIndex = _zIndex;
        this._relativePosition = _relativePosition;
        this.effectiveLineHeight = 0;
        this.effectiveLineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */) + this._relativePosition;
    }
    get lineNumber() {
        return this._lineNumber;
    }
    getDomNode() {
        const root = document.createElement('div');
        const viewModel = this._editor._getViewModel();
        const viewLineNumber = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(this._lineNumber, 1)).lineNumber;
        const lineRenderingData = viewModel.getViewLineRenderingData(viewLineNumber);
        const layoutInfo = this._editor.getLayoutInfo();
        const width = layoutInfo.width - layoutInfo.minimap.minimapCanvasOuterWidth - layoutInfo.verticalScrollbarWidth;
        const minimapSide = this._editor.getOption(67 /* EditorOption.minimap */).side;
        const lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */);
        let actualInlineDecorations;
        try {
            actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, viewLineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn);
        }
        catch (err) {
            actualInlineDecorations = [];
        }
        const renderLineInput = new RenderLineInput(true, true, lineRenderingData.content, lineRenderingData.continuesWithWrappedLine, lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0, lineRenderingData.tokens, actualInlineDecorations, lineRenderingData.tabSize, lineRenderingData.startVisibleColumn, 1, 1, 1, 500, 'none', true, true, null);
        const sb = createStringBuilder(2000);
        renderViewLine(renderLineInput, sb);
        let newLine;
        if (_ttPolicy) {
            newLine = _ttPolicy.createHTML(sb.build());
        }
        else {
            newLine = sb.build();
        }
        const lineHTMLNode = document.createElement('span');
        lineHTMLNode.className = 'sticky-line';
        lineHTMLNode.style.lineHeight = `${lineHeight}px`;
        lineHTMLNode.innerHTML = newLine;
        const lineNumberHTMLNode = document.createElement('span');
        lineNumberHTMLNode.className = 'sticky-line';
        lineNumberHTMLNode.style.lineHeight = `${lineHeight}px`;
        if (minimapSide === 'left') {
            lineNumberHTMLNode.style.width = `${layoutInfo.contentLeft - layoutInfo.minimap.minimapCanvasOuterWidth}px`;
        }
        else if (minimapSide === 'right') {
            lineNumberHTMLNode.style.width = `${layoutInfo.contentLeft}px`;
        }
        const innerLineNumberHTML = document.createElement('span');
        const lineNumberOption = this._editor.getOption(62 /* EditorOption.lineNumbers */);
        if (lineNumberOption.renderType === 1 /* RenderLineNumbersType.On */ || lineNumberOption.renderType === 3 /* RenderLineNumbersType.Interval */ && this._lineNumber % 10 === 0) {
            innerLineNumberHTML.innerText = this._lineNumber.toString();
        }
        else if (lineNumberOption.renderType === 2 /* RenderLineNumbersType.Relative */) {
            innerLineNumberHTML.innerText = Math.abs(this._lineNumber - this._editor.getPosition().lineNumber).toString();
        }
        innerLineNumberHTML.className = 'sticky-line-number';
        innerLineNumberHTML.style.lineHeight = `${lineHeight}px`;
        innerLineNumberHTML.style.width = `${layoutInfo.lineNumbersWidth}px`;
        if (minimapSide === 'left') {
            innerLineNumberHTML.style.paddingLeft = `${layoutInfo.lineNumbersLeft - layoutInfo.minimap.minimapCanvasOuterWidth}px`;
        }
        else if (minimapSide === 'right') {
            innerLineNumberHTML.style.paddingLeft = `${layoutInfo.lineNumbersLeft}px`;
        }
        lineNumberHTMLNode.appendChild(innerLineNumberHTML);
        this._register(dom.addDisposableListener(root, 'click', e => {
            e.stopPropagation();
            e.preventDefault();
            this._editor.revealPosition({ lineNumber: this._lineNumber - this._depth + 1, column: 1 });
        }));
        this._editor.applyFontInfo(lineHTMLNode);
        this._editor.applyFontInfo(innerLineNumberHTML);
        root.appendChild(lineNumberHTMLNode);
        root.appendChild(lineHTMLNode);
        root.style.zIndex = this._zIndex.toString();
        root.className = 'sticky-line-root';
        root.style.lineHeight = `${lineHeight}px`;
        root.style.width = `${width}px`;
        root.style.height = `${lineHeight}px`;
        // Special case for last line of sticky scroll
        if (this._relativePosition) {
            root.style.position = 'relative';
            root.style.top = this._relativePosition + 'px';
        }
        return root;
    }
}
class StickyScrollWidget {
    constructor(_editor) {
        this._editor = _editor;
        this.arrayOfCodeLines = [];
        this.rootDomNode = document.createElement('div');
        this.layoutInfo = this._editor.getLayoutInfo();
        this.rootDomNode = document.createElement('div');
        this.rootDomNode.className = 'sticky-widget';
        const width = this.layoutInfo.width - this.layoutInfo.minimap.minimapCanvasOuterWidth - this.layoutInfo.verticalScrollbarWidth;
        this.rootDomNode.style.width = `${width}px`;
    }
    getCurrentLines() {
        const widgetLineRange = [];
        for (const codeLine of this.arrayOfCodeLines) {
            widgetLineRange.push(codeLine.lineNumber);
        }
        return widgetLineRange;
    }
    pushCodeLine(codeLine) {
        this.arrayOfCodeLines.push(codeLine);
    }
    updateRootNode() {
        let widgetHeight = 0;
        for (const line of this.arrayOfCodeLines) {
            widgetHeight += line.effectiveLineHeight;
            this.rootDomNode.appendChild(line.getDomNode());
        }
        this.rootDomNode.style.height = widgetHeight.toString() + 'px';
    }
    emptyRootNode() {
        dispose(this.arrayOfCodeLines);
        this.arrayOfCodeLines.length = 0;
        dom.clearNode(this.rootDomNode);
    }
    getId() {
        return 'editor.contrib.stickyScrollWidget';
    }
    getDomNode() {
        const minimapSide = this._editor.getOption(67 /* EditorOption.minimap */).side;
        if (minimapSide === 'left') {
            this.rootDomNode.style.marginLeft = this._editor.getLayoutInfo().minimap.minimapCanvasOuterWidth + 'px';
        }
        else if (minimapSide === 'right') {
            this.rootDomNode.style.marginLeft = '0px';
        }
        return this.rootDomNode;
    }
    getPosition() {
        return {
            preference: null
        };
    }
}
registerEditorContribution(StickyScrollController.ID, StickyScrollController);
