/*---------------------------------------------------------------------------------------------
 *  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 * as nls from '../../../../nls.js';
import * as browser from '../../../../base/browser/browser.js';
import * as dom from '../../../../base/browser/dom.js';
import { printKeyboardEvent, printStandardKeyboardEvent, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE, KeyCodeUtils } from '../../../../base/common/keyCodes.js';
import { SimpleKeybinding, ScanCodeBinding } from '../../../../base/common/keybindings.js';
import { KeybindingParser } from '../../../../base/common/keybindingParser.js';
import { OS, isMacintosh } from '../../../../base/common/platform.js';
import { ICommandService, CommandsRegistry } from '../../../../platform/commands/common/commands.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { Extensions as ConfigExtensions } from '../../../../platform/configuration/common/configurationRegistry.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
import { Extensions } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js';
import { AbstractKeybindingService } from '../../../../platform/keybinding/common/abstractKeybindingService.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { KeybindingResolver } from '../../../../platform/keybinding/common/keybindingResolver.js';
import { KeybindingsRegistry } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { ResolvedKeybindingItem } from '../../../../platform/keybinding/common/resolvedKeybindingItem.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { ExtensionsRegistry } from '../../extensions/common/extensionsRegistry.js';
import { KeybindingIO, OutputBuilder } from '../common/keybindingIO.js';
import { IHostService } from '../../host/browser/host.js';
import { IExtensionService } from '../../extensions/common/extensions.js';
import { MenuRegistry } from '../../../../platform/actions/common/actions.js';
import { registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { commandsExtensionPoint } from '../../../api/common/menusExtensionPoint.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { parse } from '../../../../base/common/json.js';
import * as objects from '../../../../base/common/objects.js';
import { IKeyboardLayoutService } from '../../../../platform/keyboardLayout/common/keyboardLayout.js';
import { getDispatchConfig } from '../../../../platform/keyboardLayout/common/dispatchConfig.js';
import { isArray } from '../../../../base/common/types.js';
import { flatten } from '../../../../base/common/arrays.js';
import { BrowserFeatures } from '../../../../base/browser/canIUse.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { dirname } from '../../../../base/common/resources.js';
import { getAllUnboundCommands } from './unboundCommands.js';
import { UserSettingsLabelProvider } from '../../../../base/common/keybindingLabels.js';
function isContributedKeyBindingsArray(thing) {
    return Array.isArray(thing);
}
function isValidContributedKeyBinding(keyBinding, rejects) {
    if (!keyBinding) {
        rejects.push(nls.localize('nonempty', "expected non-empty value."));
        return false;
    }
    if (typeof keyBinding.command !== 'string') {
        rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
        return false;
    }
    if (keyBinding.key && typeof keyBinding.key !== 'string') {
        rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'key'));
        return false;
    }
    if (keyBinding.when && typeof keyBinding.when !== 'string') {
        rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
        return false;
    }
    if (keyBinding.mac && typeof keyBinding.mac !== 'string') {
        rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'mac'));
        return false;
    }
    if (keyBinding.linux && typeof keyBinding.linux !== 'string') {
        rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'linux'));
        return false;
    }
    if (keyBinding.win && typeof keyBinding.win !== 'string') {
        rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'win'));
        return false;
    }
    return true;
}
const keybindingType = {
    type: 'object',
    default: { command: '', key: '' },
    properties: {
        command: {
            description: nls.localize('vscode.extension.contributes.keybindings.command', 'Identifier of the command to run when keybinding is triggered.'),
            type: 'string'
        },
        args: {
            description: nls.localize('vscode.extension.contributes.keybindings.args', "Arguments to pass to the command to execute.")
        },
        key: {
            description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g. Ctrl+O and Ctrl+L L for a chord).'),
            type: 'string'
        },
        mac: {
            description: nls.localize('vscode.extension.contributes.keybindings.mac', 'Mac specific key or key sequence.'),
            type: 'string'
        },
        linux: {
            description: nls.localize('vscode.extension.contributes.keybindings.linux', 'Linux specific key or key sequence.'),
            type: 'string'
        },
        win: {
            description: nls.localize('vscode.extension.contributes.keybindings.win', 'Windows specific key or key sequence.'),
            type: 'string'
        },
        when: {
            description: nls.localize('vscode.extension.contributes.keybindings.when', 'Condition when the key is active.'),
            type: 'string'
        },
    }
};
const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint({
    extensionPoint: 'keybindings',
    deps: [commandsExtensionPoint],
    jsonSchema: {
        description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
        oneOf: [
            keybindingType,
            {
                type: 'array',
                items: keybindingType
            }
        ]
    }
});
const NUMPAD_PRINTABLE_SCANCODES = [
    90 /* NumpadDivide */,
    91 /* NumpadMultiply */,
    92 /* NumpadSubtract */,
    93 /* NumpadAdd */,
    95 /* Numpad1 */,
    96 /* Numpad2 */,
    97 /* Numpad3 */,
    98 /* Numpad4 */,
    99 /* Numpad5 */,
    100 /* Numpad6 */,
    101 /* Numpad7 */,
    102 /* Numpad8 */,
    103 /* Numpad9 */,
    104 /* Numpad0 */,
    105 /* NumpadDecimal */
];
const otherMacNumpadMapping = new Map();
otherMacNumpadMapping.set(95 /* Numpad1 */, 22 /* Digit1 */);
otherMacNumpadMapping.set(96 /* Numpad2 */, 23 /* Digit2 */);
otherMacNumpadMapping.set(97 /* Numpad3 */, 24 /* Digit3 */);
otherMacNumpadMapping.set(98 /* Numpad4 */, 25 /* Digit4 */);
otherMacNumpadMapping.set(99 /* Numpad5 */, 26 /* Digit5 */);
otherMacNumpadMapping.set(100 /* Numpad6 */, 27 /* Digit6 */);
otherMacNumpadMapping.set(101 /* Numpad7 */, 28 /* Digit7 */);
otherMacNumpadMapping.set(102 /* Numpad8 */, 29 /* Digit8 */);
otherMacNumpadMapping.set(103 /* Numpad9 */, 30 /* Digit9 */);
otherMacNumpadMapping.set(104 /* Numpad0 */, 21 /* Digit0 */);
export function resolveUserKeybindingItems(items, isDefault, keyboardMapper) {
    const result = [];
    let resultLen = 0;
    for (const item of items) {
        const when = item.when || undefined;
        const parts = item.parts;
        if (parts.length === 0) {
            // This might be a removal keybinding item in user settings => accept it
            result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, null, false);
        }
        else {
            const resolvedKeybindings = keyboardMapper.resolveUserBinding(parts);
            for (const resolvedKeybinding of resolvedKeybindings) {
                result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, null, false);
            }
        }
    }
    return result;
}
let WorkbenchKeybindingService = class WorkbenchKeybindingService extends AbstractKeybindingService {
    constructor(contextKeyService, commandService, telemetryService, notificationService, environmentService, configurationService, hostService, extensionService, fileService, logService, keyboardLayoutService) {
        super(contextKeyService, commandService, telemetryService, notificationService, logService);
        this.hostService = hostService;
        this.keyboardLayoutService = keyboardLayoutService;
        this._contributions = [];
        this.isComposingGlobalContextKey = contextKeyService.createKey('isComposing', false);
        this.updateSchema();
        let dispatchConfig = getDispatchConfig(configurationService);
        configurationService.onDidChangeConfiguration((e) => {
            const newDispatchConfig = getDispatchConfig(configurationService);
            if (dispatchConfig === newDispatchConfig) {
                return;
            }
            dispatchConfig = newDispatchConfig;
            this._keyboardMapper = this.keyboardLayoutService.getKeyboardMapper(dispatchConfig);
            this.updateResolver({ source: 1 /* Default */ });
        });
        this._keyboardMapper = this.keyboardLayoutService.getKeyboardMapper(dispatchConfig);
        this.keyboardLayoutService.onDidChangeKeyboardLayout(() => {
            this._keyboardMapper = this.keyboardLayoutService.getKeyboardMapper(dispatchConfig);
            this.updateResolver({ source: 1 /* Default */ });
        });
        this._cachedResolver = null;
        this.userKeybindings = this._register(new UserKeybindings(environmentService.keybindingsResource, fileService, logService));
        this.userKeybindings.initialize().then(() => {
            if (this.userKeybindings.keybindings.length) {
                this.updateResolver({ source: 2 /* User */ });
            }
        });
        this._register(this.userKeybindings.onDidChange(() => {
            logService.debug('User keybindings changed');
            this.updateResolver({
                source: 2 /* User */,
                keybindings: this.userKeybindings.keybindings
            });
        }));
        keybindingsExtPoint.setHandler((extensions) => {
            const keybindings = [];
            for (const extension of extensions) {
                this._handleKeybindingsExtensionPointUser(extension.description.identifier, extension.description.isBuiltin, extension.value, extension.collector, keybindings);
            }
            KeybindingsRegistry.setExtensionKeybindings(keybindings);
            this.updateResolver({ source: 1 /* Default */ });
        });
        this.updateSchema();
        this._register(extensionService.onDidRegisterExtensions(() => this.updateSchema()));
        // for standard keybindings
        this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e) => {
            this.isComposingGlobalContextKey.set(e.isComposing);
            const keyEvent = new StandardKeyboardEvent(e);
            this._log(`/ Received  keydown event - ${printKeyboardEvent(e)}`);
            this._log(`| Converted keydown event - ${printStandardKeyboardEvent(keyEvent)}`);
            const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
            if (shouldPreventDefault) {
                keyEvent.preventDefault();
            }
            this.isComposingGlobalContextKey.set(false);
        }));
        // for single modifier chord keybindings (e.g. shift shift)
        this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e) => {
            this.isComposingGlobalContextKey.set(e.isComposing);
            const keyEvent = new StandardKeyboardEvent(e);
            const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target);
            if (shouldPreventDefault) {
                keyEvent.preventDefault();
            }
            this.isComposingGlobalContextKey.set(false);
        }));
        const data = this.keyboardLayoutService.getCurrentKeyboardLayout();
        /* __GDPR__FRAGMENT__
            "IKeyboardLayoutInfo" : {
                "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
            }
        */
        /* __GDPR__FRAGMENT__
            "IKeyboardLayoutInfo" : {
                "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
            }
        */
        /* __GDPR__FRAGMENT__
            "IKeyboardLayoutInfo" : {
                "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
                "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
            }
        */
        /* __GDPR__
            "keyboardLayout" : {
                "currentKeyboardLayout": { "${inline}": [ "${IKeyboardLayoutInfo}" ] }
            }
        */
        telemetryService.publicLog('keyboardLayout', {
            currentKeyboardLayout: data
        });
        this._register(browser.onDidChangeFullscreen(() => {
            const keyboard = navigator.keyboard;
            if (BrowserFeatures.keyboard === 2 /* None */) {
                return;
            }
            if (browser.isFullscreen()) {
                keyboard === null || keyboard === void 0 ? void 0 : keyboard.lock(['Escape']);
            }
            else {
                keyboard === null || keyboard === void 0 ? void 0 : keyboard.unlock();
            }
            // update resolver which will bring back all unbound keyboard shortcuts
            this._cachedResolver = null;
            this._onDidUpdateKeybindings.fire({ source: 2 /* User */ });
        }));
    }
    registerSchemaContribution(contribution) {
        this._contributions.push(contribution);
        if (contribution.onDidChange) {
            this._register(contribution.onDidChange(() => this.updateSchema()));
        }
        this.updateSchema();
    }
    updateSchema() {
        updateSchema(flatten(this._contributions.map(x => x.getSchemaAdditions())));
    }
    _printUserBinding(parts) {
        return UserSettingsLabelProvider.toLabel(OS, parts, (part) => {
            if (part instanceof SimpleKeybinding) {
                return KeyCodeUtils.toString(part.keyCode);
            }
            return ScanCodeUtils.toString(part.scanCode);
        }) || '[null]';
    }
    _printResolvedKeybinding(resolvedKeybinding) {
        return resolvedKeybinding.getDispatchParts().map(x => x || '[null]').join(' ');
    }
    _printResolvedKeybindings(output, input, resolvedKeybindings) {
        const padLength = 35;
        const firstRow = `${input.padStart(padLength, ' ')} => `;
        if (resolvedKeybindings.length === 0) {
            // no binding found
            output.push(`${firstRow}${'[NO BINDING]'.padStart(padLength, ' ')}`);
            return;
        }
        const firstRowIndentation = firstRow.length;
        let isFirst = true;
        for (const resolvedKeybinding of resolvedKeybindings) {
            if (isFirst) {
                output.push(`${firstRow}${this._printResolvedKeybinding(resolvedKeybinding).padStart(padLength, ' ')}`);
            }
            else {
                output.push(`${' '.repeat(firstRowIndentation)}${this._printResolvedKeybinding(resolvedKeybinding).padStart(padLength, ' ')}`);
            }
        }
    }
    _dumpResolveKeybindingDebugInfo() {
        const seenBindings = new Set();
        const result = [];
        result.push(`Default Resolved Keybindings (unique only):`);
        for (const item of KeybindingsRegistry.getDefaultKeybindings()) {
            if (!item.keybinding || item.keybinding.length === 0) {
                continue;
            }
            const input = this._printUserBinding(item.keybinding);
            if (seenBindings.has(input)) {
                continue;
            }
            seenBindings.add(input);
            const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(item.keybinding);
            this._printResolvedKeybindings(result, input, resolvedKeybindings);
        }
        result.push(`User Resolved Keybindings (unique only):`);
        for (const _item of this.userKeybindings.keybindings) {
            const item = KeybindingIO.readUserKeybindingItem(_item);
            if (!item.parts || item.parts.length === 0) {
                continue;
            }
            const input = _item.key;
            if (seenBindings.has(input)) {
                continue;
            }
            seenBindings.add(input);
            const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(item.parts);
            this._printResolvedKeybindings(result, input, resolvedKeybindings);
        }
        return result.join('\n');
    }
    _dumpDebugInfo() {
        const layoutInfo = JSON.stringify(this.keyboardLayoutService.getCurrentKeyboardLayout(), null, '\t');
        const mapperInfo = this._keyboardMapper.dumpDebugInfo();
        const resolvedKeybindings = this._dumpResolveKeybindingDebugInfo();
        const rawMapping = JSON.stringify(this.keyboardLayoutService.getRawKeyboardMapping(), null, '\t');
        return `Layout info:\n${layoutInfo}\n\n${resolvedKeybindings}\n\n${mapperInfo}\n\nRaw mapping:\n${rawMapping}`;
    }
    _dumpDebugInfoJSON() {
        const info = {
            layout: this.keyboardLayoutService.getCurrentKeyboardLayout(),
            rawMapping: this.keyboardLayoutService.getRawKeyboardMapping()
        };
        return JSON.stringify(info, null, '\t');
    }
    customKeybindingsCount() {
        return this.userKeybindings.keybindings.length;
    }
    updateResolver(event) {
        this._cachedResolver = null;
        this._onDidUpdateKeybindings.fire(event);
    }
    _getResolver() {
        if (!this._cachedResolver) {
            const defaults = this._resolveKeybindingItems(KeybindingsRegistry.getDefaultKeybindings(), true);
            const overrides = this._resolveUserKeybindingItems(this.userKeybindings.keybindings.map((k) => KeybindingIO.readUserKeybindingItem(k)), false);
            this._cachedResolver = new KeybindingResolver(defaults, overrides, (str) => this._log(str));
        }
        return this._cachedResolver;
    }
    _documentHasFocus() {
        // it is possible that the document has lost focus, but the
        // window is still focused, e.g. when a <webview> element
        // has focus
        return this.hostService.hasFocus;
    }
    _resolveKeybindingItems(items, isDefault) {
        const result = [];
        let resultLen = 0;
        for (const item of items) {
            const when = item.when || undefined;
            const keybinding = item.keybinding;
            if (!keybinding) {
                // This might be a removal keybinding item in user settings => accept it
                result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, item.extensionId, item.isBuiltinExtension);
            }
            else {
                if (this._assertBrowserConflicts(keybinding, item.command)) {
                    continue;
                }
                const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(keybinding);
                for (let i = resolvedKeybindings.length - 1; i >= 0; i--) {
                    const resolvedKeybinding = resolvedKeybindings[i];
                    result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, item.extensionId, item.isBuiltinExtension);
                }
            }
        }
        return result;
    }
    _resolveUserKeybindingItems(items, isDefault) {
        return resolveUserKeybindingItems(items, isDefault, this._keyboardMapper);
    }
    _assertBrowserConflicts(kb, commandId) {
        if (BrowserFeatures.keyboard === 0 /* Always */) {
            return false;
        }
        if (BrowserFeatures.keyboard === 1 /* FullScreen */ && browser.isFullscreen()) {
            return false;
        }
        for (const part of kb) {
            if (!part.metaKey && !part.altKey && !part.ctrlKey && !part.shiftKey) {
                continue;
            }
            const modifiersMask = 2048 /* CtrlCmd */ | 512 /* Alt */ | 1024 /* Shift */;
            let partModifiersMask = 0;
            if (part.metaKey) {
                partModifiersMask |= 2048 /* CtrlCmd */;
            }
            if (part.shiftKey) {
                partModifiersMask |= 1024 /* Shift */;
            }
            if (part.altKey) {
                partModifiersMask |= 512 /* Alt */;
            }
            if (part.ctrlKey && OS === 2 /* Macintosh */) {
                partModifiersMask |= 256 /* WinCtrl */;
            }
            if ((partModifiersMask & modifiersMask) === (2048 /* CtrlCmd */ | 512 /* Alt */)) {
                if (part instanceof ScanCodeBinding && (part.scanCode === 86 /* ArrowLeft */ || part.scanCode === 85 /* ArrowRight */)) {
                    // console.warn('Ctrl/Cmd+Arrow keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId);
                    return true;
                }
                if (part instanceof SimpleKeybinding && (part.keyCode === 15 /* LeftArrow */ || part.keyCode === 17 /* RightArrow */)) {
                    // console.warn('Ctrl/Cmd+Arrow keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId);
                    return true;
                }
            }
            if ((partModifiersMask & modifiersMask) === 2048 /* CtrlCmd */) {
                if (part instanceof ScanCodeBinding && (part.scanCode >= 36 /* Digit1 */ && part.scanCode <= 45 /* Digit0 */)) {
                    // console.warn('Ctrl/Cmd+Num keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId);
                    return true;
                }
                if (part instanceof SimpleKeybinding && (part.keyCode >= 21 /* Digit0 */ && part.keyCode <= 30 /* Digit9 */)) {
                    // console.warn('Ctrl/Cmd+Num keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId);
                    return true;
                }
            }
        }
        return false;
    }
    resolveKeybinding(kb) {
        return this._keyboardMapper.resolveKeybinding(kb);
    }
    resolveKeyboardEvent(keyboardEvent) {
        this.keyboardLayoutService.validateCurrentKeyboardMapping(keyboardEvent);
        return this._keyboardMapper.resolveKeyboardEvent(keyboardEvent);
    }
    resolveUserBinding(userBinding) {
        const parts = KeybindingParser.parseUserBinding(userBinding);
        return this._keyboardMapper.resolveUserBinding(parts);
    }
    _handleKeybindingsExtensionPointUser(extensionId, isBuiltin, keybindings, collector, result) {
        if (isContributedKeyBindingsArray(keybindings)) {
            for (let i = 0, len = keybindings.length; i < len; i++) {
                this._handleKeybinding(extensionId, isBuiltin, i + 1, keybindings[i], collector, result);
            }
        }
        else {
            this._handleKeybinding(extensionId, isBuiltin, 1, keybindings, collector, result);
        }
    }
    _handleKeybinding(extensionId, isBuiltin, idx, keybindings, collector, result) {
        const rejects = [];
        if (isValidContributedKeyBinding(keybindings, rejects)) {
            const rule = this._asCommandRule(extensionId, isBuiltin, idx++, keybindings);
            if (rule) {
                result.push(rule);
            }
        }
        if (rejects.length > 0) {
            collector.error(nls.localize('invalid.keybindings', "Invalid `contributes.{0}`: {1}", keybindingsExtPoint.name, rejects.join('\n')));
        }
    }
    static bindToCurrentPlatform(key, mac, linux, win) {
        if (OS === 1 /* Windows */ && win) {
            if (win) {
                return win;
            }
        }
        else if (OS === 2 /* Macintosh */) {
            if (mac) {
                return mac;
            }
        }
        else {
            if (linux) {
                return linux;
            }
        }
        return key;
    }
    _asCommandRule(extensionId, isBuiltin, idx, binding) {
        const { command, args, when, key, mac, linux, win } = binding;
        const keybinding = WorkbenchKeybindingService.bindToCurrentPlatform(key, mac, linux, win);
        if (!keybinding) {
            return undefined;
        }
        let weight;
        if (isBuiltin) {
            weight = 300 /* BuiltinExtension */ + idx;
        }
        else {
            weight = 400 /* ExternalExtension */ + idx;
        }
        const commandAction = MenuRegistry.getCommand(command);
        const precondition = commandAction && commandAction.precondition;
        let fullWhen;
        if (when && precondition) {
            fullWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(when));
        }
        else if (when) {
            fullWhen = ContextKeyExpr.deserialize(when);
        }
        else if (precondition) {
            fullWhen = precondition;
        }
        const desc = {
            id: command,
            args,
            when: fullWhen,
            weight: weight,
            keybinding: KeybindingParser.parseUserBinding(keybinding),
            extensionId: extensionId.value,
            isBuiltinExtension: isBuiltin
        };
        return desc;
    }
    getDefaultKeybindingsContent() {
        const resolver = this._getResolver();
        const defaultKeybindings = resolver.getDefaultKeybindings();
        const boundCommands = resolver.getDefaultBoundCommands();
        return (WorkbenchKeybindingService._getDefaultKeybindings(defaultKeybindings)
            + '\n\n'
            + WorkbenchKeybindingService._getAllCommandsAsComment(boundCommands));
    }
    static _getDefaultKeybindings(defaultKeybindings) {
        const out = new OutputBuilder();
        out.writeLine('[');
        const lastIndex = defaultKeybindings.length - 1;
        defaultKeybindings.forEach((k, index) => {
            KeybindingIO.writeKeybindingItem(out, k);
            if (index !== lastIndex) {
                out.writeLine(',');
            }
            else {
                out.writeLine();
            }
        });
        out.writeLine(']');
        return out.toString();
    }
    static _getAllCommandsAsComment(boundCommands) {
        const unboundCommands = getAllUnboundCommands(boundCommands);
        const pretty = unboundCommands.sort().join('\n// - ');
        return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty;
    }
    mightProducePrintableCharacter(event) {
        if (event.ctrlKey || event.metaKey || event.altKey) {
            // ignore ctrl/cmd/alt-combination but not shift-combinatios
            return false;
        }
        const code = ScanCodeUtils.toEnum(event.code);
        if (NUMPAD_PRINTABLE_SCANCODES.indexOf(code) !== -1) {
            // This is a numpad key that might produce a printable character based on NumLock.
            // Let's check if NumLock is on or off based on the event's keyCode.
            // e.g.
            // - when NumLock is off, ScanCode.Numpad4 produces KeyCode.LeftArrow
            // - when NumLock is on, ScanCode.Numpad4 produces KeyCode.NUMPAD_4
            // However, ScanCode.NumpadAdd always produces KeyCode.NUMPAD_ADD
            if (event.keyCode === IMMUTABLE_CODE_TO_KEY_CODE[code]) {
                // NumLock is on or this is /, *, -, + on the numpad
                return true;
            }
            if (isMacintosh && event.keyCode === otherMacNumpadMapping.get(code)) {
                // on macOS, the numpad keys can also map to keys 1 - 0.
                return true;
            }
            return false;
        }
        const keycode = IMMUTABLE_CODE_TO_KEY_CODE[code];
        if (keycode !== -1) {
            // https://github.com/microsoft/vscode/issues/74934
            return false;
        }
        // consult the KeyboardMapperFactory to check the given event for
        // a printable value.
        const mapping = this.keyboardLayoutService.getRawKeyboardMapping();
        if (!mapping) {
            return false;
        }
        const keyInfo = mapping[event.code];
        if (!keyInfo) {
            return false;
        }
        if (!keyInfo.value || /\s/.test(keyInfo.value)) {
            return false;
        }
        return true;
    }
};
WorkbenchKeybindingService = __decorate([
    __param(0, IContextKeyService),
    __param(1, ICommandService),
    __param(2, ITelemetryService),
    __param(3, INotificationService),
    __param(4, IEnvironmentService),
    __param(5, IConfigurationService),
    __param(6, IHostService),
    __param(7, IExtensionService),
    __param(8, IFileService),
    __param(9, ILogService),
    __param(10, IKeyboardLayoutService)
], WorkbenchKeybindingService);
export { WorkbenchKeybindingService };
class UserKeybindings extends Disposable {
    constructor(keybindingsResource, fileService, logService) {
        super();
        this.keybindingsResource = keybindingsResource;
        this.fileService = fileService;
        this._keybindings = [];
        this._onDidChange = this._register(new Emitter());
        this.onDidChange = this._onDidChange.event;
        this._register(fileService.watch(dirname(keybindingsResource)));
        // Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134
        this._register(this.fileService.watch(this.keybindingsResource));
        this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(changed => {
            if (changed) {
                this._onDidChange.fire();
            }
        }), 50));
        this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.keybindingsResource))(() => {
            logService.debug('Keybindings file changed');
            this.reloadConfigurationScheduler.schedule();
        }));
    }
    get keybindings() { return this._keybindings; }
    initialize() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.reload();
        });
    }
    reload() {
        return __awaiter(this, void 0, void 0, function* () {
            const existing = this._keybindings;
            try {
                const content = yield this.fileService.readFile(this.keybindingsResource);
                const value = parse(content.value.toString());
                this._keybindings = isArray(value) ? value : [];
            }
            catch (e) {
                this._keybindings = [];
            }
            return existing ? !objects.equals(existing, this._keybindings) : true;
        });
    }
}
const schemaId = 'vscode://schemas/keybindings';
const commandsSchemas = [];
const commandsEnum = [];
const commandsEnumDescriptions = [];
const schema = {
    id: schemaId,
    type: 'array',
    title: nls.localize('keybindings.json.title', "Keybindings configuration"),
    allowTrailingCommas: true,
    allowComments: true,
    definitions: {
        'editorGroupsSchema': {
            'type': 'array',
            'items': {
                'type': 'object',
                'properties': {
                    'groups': {
                        '$ref': '#/definitions/editorGroupsSchema',
                        'default': [{}, {}]
                    },
                    'size': {
                        'type': 'number',
                        'default': 0.5
                    }
                }
            }
        }
    },
    items: {
        'required': ['key'],
        'type': 'object',
        'defaultSnippets': [{ 'body': { 'key': '$1', 'command': '$2', 'when': '$3' } }],
        'properties': {
            'key': {
                'type': 'string',
                'description': nls.localize('keybindings.json.key', "Key or key sequence (separated by space)"),
            },
            'command': {
                'anyOf': [
                    {
                        'type': 'string',
                        'enum': commandsEnum,
                        'enumDescriptions': commandsEnumDescriptions,
                        'description': nls.localize('keybindings.json.command', "Name of the command to execute"),
                    },
                    {
                        'type': 'string'
                    }
                ]
            },
            'when': {
                'type': 'string',
                'description': nls.localize('keybindings.json.when', "Condition when the key is active.")
            },
            'args': {
                'description': nls.localize('keybindings.json.args', "Arguments to pass to the command to execute.")
            }
        },
        'allOf': commandsSchemas
    }
};
const schemaRegistry = Registry.as(Extensions.JSONContribution);
schemaRegistry.registerSchema(schemaId, schema);
function updateSchema(additionalContributions) {
    commandsSchemas.length = 0;
    commandsEnum.length = 0;
    commandsEnumDescriptions.length = 0;
    const knownCommands = new Set();
    const addKnownCommand = (commandId, description) => {
        if (!/^_/.test(commandId)) {
            if (!knownCommands.has(commandId)) {
                knownCommands.add(commandId);
                commandsEnum.push(commandId);
                commandsEnumDescriptions.push(description);
                // Also add the negative form for keybinding removal
                commandsEnum.push(`-${commandId}`);
                commandsEnumDescriptions.push(description);
            }
        }
    };
    const allCommands = CommandsRegistry.getCommands();
    for (const [commandId, command] of allCommands) {
        const commandDescription = command.description;
        addKnownCommand(commandId, commandDescription ? commandDescription.description : undefined);
        if (!commandDescription || !commandDescription.args || commandDescription.args.length !== 1 || !commandDescription.args[0].schema) {
            continue;
        }
        const argsSchema = commandDescription.args[0].schema;
        const argsRequired = ((typeof commandDescription.args[0].isOptional !== 'undefined')
            ? (!commandDescription.args[0].isOptional)
            : (Array.isArray(argsSchema.required) && argsSchema.required.length > 0));
        const addition = {
            'if': {
                'properties': {
                    'command': { 'const': commandId }
                }
            },
            'then': {
                'required': [].concat(argsRequired ? ['args'] : []),
                'properties': {
                    'args': argsSchema
                }
            }
        };
        commandsSchemas.push(addition);
    }
    const menuCommands = MenuRegistry.getCommands();
    for (const commandId of menuCommands.keys()) {
        addKnownCommand(commandId);
    }
    commandsSchemas.push(...additionalContributions);
    schemaRegistry.notifySchemaChanged(schemaId);
}
const configurationRegistry = Registry.as(ConfigExtensions.Configuration);
const keyboardConfiguration = {
    'id': 'keyboard',
    'order': 15,
    'type': 'object',
    'title': nls.localize('keyboardConfigurationTitle', "Keyboard"),
    'properties': {
        'keyboard.dispatch': {
            scope: 1 /* APPLICATION */,
            type: 'string',
            enum: ['code', 'keyCode'],
            default: 'code',
            markdownDescription: nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."),
            included: OS === 2 /* Macintosh */ || OS === 3 /* Linux */
        }
    }
};
configurationRegistry.registerConfiguration(keyboardConfiguration);
registerSingleton(IKeybindingService, WorkbenchKeybindingService);
