/*---------------------------------------------------------------------------------------------
 *  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 { RunOnceScheduler } from '../../../base/common/async';
import { Disposable } from '../../../base/common/lifecycle';
import { InvisibleCharacters } from '../../../base/common/strings';
import './unicodeHighlighter.css';
import { EditorAction, registerEditorAction, registerEditorContribution } from '../../browser/editorExtensions';
import { deriveFromWorkspaceTrust, unicodeHighlightConfigKeys } from '../../common/config/editorOptions';
import { MinimapPosition, OverviewRulerLane } from '../../common/model';
import { ModelDecorationOptions } from '../../common/model/textModel';
import { UnicodeTextModelHighlighter } from '../../common/modes/unicodeTextModelHighlighter';
import { IEditorWorkerService } from '../../common/services/editorWorkerService';
import { IModeService } from '../../common/services/modeService';
import { MarkdownHover, renderMarkdownHovers } from '../hover/markdownHoverParticipant';
import * as nls from '../../../nls';
import { IConfigurationService } from '../../../platform/configuration/common/configuration';
import { IOpenerService } from '../../../platform/opener/common/opener';
import { IQuickInputService } from '../../../platform/quickinput/common/quickInput';
import { minimapFindMatch, minimapUnicodeHighlight, overviewRulerFindMatchForeground, overviewRulerUnicodeHighlightForeground } from '../../../platform/theme/common/colorRegistry';
import { themeColorFromId } from '../../../platform/theme/common/themeService';
import { IWorkspaceTrustManagementService } from '../../../platform/workspace/common/workspaceTrust';
let UnicodeHighlighter = class UnicodeHighlighter extends Disposable {
    constructor(_editor, _editorWorkerService, _workspaceTrustService) {
        super();
        this._editor = _editor;
        this._editorWorkerService = _editorWorkerService;
        this._workspaceTrustService = _workspaceTrustService;
        this._highlighter = null;
        this._register(this._editor.onDidChangeModel(() => {
            this._updateHighlighter();
        }));
        this._options = _editor.getOption(112 /* unicodeHighlighting */);
        this._register(_workspaceTrustService.onDidChangeTrust(e => {
            this._updateHighlighter();
        }));
        this._register(_editor.onDidChangeConfiguration(e => {
            if (e.hasChanged(112 /* unicodeHighlighting */)) {
                this._options = _editor.getOption(112 /* unicodeHighlighting */);
                this._updateHighlighter();
            }
        }));
        this._updateHighlighter();
    }
    dispose() {
        if (this._highlighter) {
            this._highlighter.dispose();
            this._highlighter = null;
        }
        super.dispose();
    }
    _updateHighlighter() {
        if (this._highlighter) {
            this._highlighter.dispose();
            this._highlighter = null;
        }
        if (!this._editor.hasModel()) {
            return;
        }
        const options = resolveOptions(this._workspaceTrustService.isWorkspaceTrusted(), this._options);
        if ([
            options.nonBasicASCII,
            options.ambiguousCharacters,
            options.invisibleCharacters,
        ].every((option) => option === false)) {
            // Don't do anything if the feature is fully disabled
            return;
        }
        const highlightOptions = {
            nonBasicASCII: options.nonBasicASCII,
            ambiguousCharacters: options.ambiguousCharacters,
            invisibleCharacters: options.invisibleCharacters,
            includeComments: options.includeComments,
            allowedCodePoints: Array.from(options.allowedCharacters).map(c => c.codePointAt(0)),
        };
        if (this._editorWorkerService.canComputeUnicodeHighlights(this._editor.getModel().uri)) {
            this._highlighter = new DocumentUnicodeHighlighter(this._editor, highlightOptions, this._editorWorkerService);
        }
        else {
            this._highlighter = new ViewportUnicodeHighlighter(this._editor, highlightOptions);
        }
    }
    getDecorationInfo(decorationId) {
        if (this._highlighter) {
            return this._highlighter.getDecorationInfo(decorationId);
        }
        return null;
    }
};
UnicodeHighlighter.ID = 'editor.contrib.unicodeHighlighter';
UnicodeHighlighter = __decorate([
    __param(1, IEditorWorkerService),
    __param(2, IWorkspaceTrustManagementService)
], UnicodeHighlighter);
export { UnicodeHighlighter };
function resolveOptions(trusted, options) {
    var _a;
    let defaults;
    if (trusted) {
        defaults = {
            nonBasicASCII: false,
            ambiguousCharacters: true,
            invisibleCharacters: true,
            includeComments: true,
        };
    }
    else {
        defaults = {
            nonBasicASCII: true,
            ambiguousCharacters: true,
            invisibleCharacters: true,
            includeComments: false,
        };
    }
    return {
        nonBasicASCII: options.nonBasicASCII !== deriveFromWorkspaceTrust ? options.nonBasicASCII : defaults.nonBasicASCII,
        ambiguousCharacters: options.ambiguousCharacters !== deriveFromWorkspaceTrust ? options.ambiguousCharacters : defaults.ambiguousCharacters,
        invisibleCharacters: options.invisibleCharacters !== deriveFromWorkspaceTrust ? options.invisibleCharacters : defaults.invisibleCharacters,
        includeComments: options.includeComments !== deriveFromWorkspaceTrust ? options.includeComments : defaults.includeComments,
        allowedCharacters: (_a = options.allowedCharacters) !== null && _a !== void 0 ? _a : [],
    };
}
let DocumentUnicodeHighlighter = class DocumentUnicodeHighlighter extends Disposable {
    constructor(_editor, _options, _editorWorkerService) {
        super();
        this._editor = _editor;
        this._options = _options;
        this._editorWorkerService = _editorWorkerService;
        this._model = this._editor.getModel();
        this._decorationIds = new Set();
        this._updateSoon = this._register(new RunOnceScheduler(() => this._update(), 250));
        this._register(this._editor.onDidChangeModelContent(() => {
            this._updateSoon.schedule();
        }));
        this._updateSoon.schedule();
    }
    dispose() {
        this._decorationIds = new Set(this._model.deltaDecorations(Array.from(this._decorationIds), []));
        super.dispose();
    }
    _update() {
        if (!this._model.mightContainNonBasicASCII()) {
            this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), []));
            return;
        }
        const modelVersionId = this._model.getVersionId();
        this._editorWorkerService
            .computedUnicodeHighlights(this._model.uri, this._options)
            .then((ranges) => {
            if (this._model.getVersionId() !== modelVersionId) {
                // model changed in the meantime
                return;
            }
            const decorations = [];
            for (const range of ranges) {
                decorations.push({ range: range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
            }
            this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), decorations));
        });
    }
    getDecorationInfo(decorationId) {
        if (!this._decorationIds.has(decorationId)) {
            return null;
        }
        const range = this._editor.getModel().getDecorationRange(decorationId);
        const text = this._editor.getModel().getValueInRange(range);
        return {
            reason: computeReason(text, this._options),
        };
    }
};
DocumentUnicodeHighlighter = __decorate([
    __param(2, IEditorWorkerService)
], DocumentUnicodeHighlighter);
class ViewportUnicodeHighlighter extends Disposable {
    constructor(_editor, _options) {
        super();
        this._editor = _editor;
        this._options = _options;
        this._model = this._editor.getModel();
        this._decorationIds = new Set();
        this._updateSoon = this._register(new RunOnceScheduler(() => this._update(), 250));
        this._register(this._editor.onDidLayoutChange(() => {
            this._updateSoon.schedule();
        }));
        this._register(this._editor.onDidScrollChange(() => {
            this._updateSoon.schedule();
        }));
        this._register(this._editor.onDidChangeHiddenAreas(() => {
            this._updateSoon.schedule();
        }));
        this._register(this._editor.onDidChangeModelContent(() => {
            this._updateSoon.schedule();
        }));
        this._updateSoon.schedule();
    }
    dispose() {
        this._decorationIds = new Set(this._model.deltaDecorations(Array.from(this._decorationIds), []));
        super.dispose();
    }
    _update() {
        if (!this._model.mightContainNonBasicASCII()) {
            this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), []));
            return;
        }
        const ranges = this._editor.getVisibleRanges();
        const decorations = [];
        for (const range of ranges) {
            const ranges = UnicodeTextModelHighlighter.computeUnicodeHighlights(this._model, this._options, range);
            for (const range of ranges) {
                decorations.push({ range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
            }
        }
        this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), decorations));
    }
    getDecorationInfo(decorationId) {
        if (!this._decorationIds.has(decorationId)) {
            return null;
        }
        const range = this._editor.getModel().getDecorationRange(decorationId);
        const text = this._editor.getModel().getValueInRange(range);
        return {
            reason: computeReason(text, this._options),
        };
    }
}
let UnicodeHighlighterHoverParticipant = class UnicodeHighlighterHoverParticipant {
    constructor(_editor, _hover, _modeService, _openerService) {
        this._editor = _editor;
        this._hover = _hover;
        this._modeService = _modeService;
        this._openerService = _openerService;
    }
    computeSync(anchor, lineDecorations) {
        if (!this._editor.hasModel() || anchor.type !== 1 /* Range */) {
            return [];
        }
        const model = this._editor.getModel();
        const unicodeHighlighter = this._editor.getContribution(UnicodeHighlighter.ID);
        const result = [];
        let index = 300;
        for (const d of lineDecorations) {
            const highlightInfo = unicodeHighlighter.getDecorationInfo(d.id);
            if (!highlightInfo) {
                continue;
            }
            const char = model.getValueInRange(d.range);
            // text refers to a single character.
            const codePoint = char.codePointAt(0);
            function formatCodePoint(codePoint) {
                let value = `\`U+${codePoint.toString(16).padStart(4, '0')}\``;
                if (!InvisibleCharacters.isInvisibleCharacter(codePoint)) {
                    // Don't render any control characters or any invisible characters, as they cannot be seen anyways.
                    value += ` "${`${renderCodePointAsInlineCode(codePoint)}`}"`;
                }
                return value;
            }
            const codePointStr = formatCodePoint(codePoint);
            let reason;
            switch (highlightInfo.reason.kind) {
                case 0 /* Ambiguous */:
                    reason = nls.localize('unicodeHighlight.characterIsAmbiguous', 'The character {0} could be confused with the character {1}, which is more common in source code.', codePointStr, formatCodePoint(highlightInfo.reason.confusableWith.codePointAt(0)));
                    break;
                case 1 /* Invisible */:
                    reason = nls.localize('unicodeHighlight.characterIsInvisible', 'The character {0} is invisible.', codePointStr);
                    break;
                case 2 /* NonBasicAscii */:
                    reason = nls.localize('unicodeHighlight.characterIsNonBasicAscii', 'The character {0} is not a basic ASCII character.', codePoint);
                    break;
            }
            const adjustSettingsArgs = {
                codePoint: codePoint,
                reason: highlightInfo.reason.kind,
            };
            const adjustSettings = nls.localize('unicodeHighlight.adjustSettings', 'Adjust settings');
            const contents = [{
                    value: `${reason} [${adjustSettings}](command:${ShowExcludeOptions.ID}?${encodeURIComponent(JSON.stringify(adjustSettingsArgs))})`,
                    isTrusted: true,
                }];
            result.push(new MarkdownHover(this, d.range, contents, index++));
        }
        return result;
    }
    renderHoverParts(hoverParts, fragment, statusBar) {
        return renderMarkdownHovers(hoverParts, fragment, this._editor, this._hover, this._modeService, this._openerService);
    }
};
UnicodeHighlighterHoverParticipant = __decorate([
    __param(2, IModeService),
    __param(3, IOpenerService)
], UnicodeHighlighterHoverParticipant);
export { UnicodeHighlighterHoverParticipant };
function renderCodePointAsInlineCode(codePoint) {
    if (codePoint === 96 /* BackTick */) {
        return '`` ` ``';
    }
    return '`' + String.fromCodePoint(codePoint) + '`';
}
function computeReason(char, options) {
    return UnicodeTextModelHighlighter.computeUnicodeHighlightReason(char, options);
}
const DECORATION_HIDE_IN_COMMENTS = ModelDecorationOptions.register({
    description: 'unicode-highlight',
    stickiness: 1 /* NeverGrowsWhenTypingAtEdges */,
    className: 'unicode-highlight',
    showIfCollapsed: true,
    overviewRuler: {
        color: themeColorFromId(overviewRulerUnicodeHighlightForeground),
        position: OverviewRulerLane.Center
    },
    minimap: {
        color: themeColorFromId(minimapUnicodeHighlight),
        position: MinimapPosition.Inline
    },
    hideInCommentTokens: true
});
const DECORATION = ModelDecorationOptions.register({
    description: 'unicode-highlight',
    stickiness: 1 /* NeverGrowsWhenTypingAtEdges */,
    className: 'unicode-highlight',
    showIfCollapsed: true,
    overviewRuler: {
        color: themeColorFromId(overviewRulerFindMatchForeground),
        position: OverviewRulerLane.Center
    },
    minimap: {
        color: themeColorFromId(minimapFindMatch),
        position: MinimapPosition.Inline
    }
});
export class ShowExcludeOptions extends EditorAction {
    constructor() {
        super({
            id: ShowExcludeOptions.ID,
            label: nls.localize('action.unicodeHighlight.showExcludeOptions', "Show Exclude Options"),
            alias: 'Show Exclude Options',
            precondition: undefined
        });
    }
    run(accessor, editor, args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { codePoint, reason } = args;
            const char = String.fromCodePoint(codePoint);
            const quickPickService = accessor.get(IQuickInputService);
            const configurationService = accessor.get(IConfigurationService);
            const options = [
                {
                    label: nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `U+${codePoint.toString(16)} "${char}"`),
                    run: () => __awaiter(this, void 0, void 0, function* () {
                        const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters);
                        let value;
                        if (typeof existingValue === 'string') {
                            value = existingValue;
                        }
                        else {
                            value = '';
                        }
                        value += char;
                        yield configurationService.updateValue(unicodeHighlightConfigKeys.allowedCharacters, value, 1 /* USER */);
                    })
                },
            ];
            if (reason === 0 /* Ambiguous */) {
                options.push({
                    label: nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable highlighting of ambiguous characters'),
                    run: () => __awaiter(this, void 0, void 0, function* () {
                        yield configurationService.updateValue(unicodeHighlightConfigKeys.ambiguousCharacters, false, 1 /* USER */);
                    })
                });
            }
            else if (reason === 1 /* Invisible */) {
                options.push({
                    label: nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'),
                    run: () => __awaiter(this, void 0, void 0, function* () {
                        yield configurationService.updateValue(unicodeHighlightConfigKeys.invisibleCharacters, false, 1 /* USER */);
                    })
                });
            }
            else if (reason === 2 /* NonBasicAscii */) {
                options.push({
                    label: nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'),
                    run: () => __awaiter(this, void 0, void 0, function* () {
                        yield configurationService.updateValue(unicodeHighlightConfigKeys.nonBasicASCII, false, 1 /* USER */);
                    })
                });
            }
            else {
                expectNever(reason);
            }
            const result = yield quickPickService.pick(options, { title: nls.localize('unicodeHighlight.configureUnicodeHighlightOptions', 'Configure Unicode Highlight Options') });
            if (result) {
                yield result.run();
            }
        });
    }
}
ShowExcludeOptions.ID = 'editor.action.unicodeHighlight.showExcludeOptions';
function expectNever(value) {
    throw new Error(`Unexpected value: ${value}`);
}
registerEditorAction(ShowExcludeOptions);
registerEditorContribution(UnicodeHighlighter.ID, UnicodeHighlighter);
