Path: blob/main/src/vs/platform/keybinding/common/keybindingsRegistry.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { decodeKeybinding, Keybinding } from '../../../base/common/keybindings.js';6import { OperatingSystem, OS } from '../../../base/common/platform.js';7import { CommandsRegistry, ICommandHandler, ICommandMetadata } from '../../commands/common/commands.js';8import { ContextKeyExpression } from '../../contextkey/common/contextkey.js';9import { Registry } from '../../registry/common/platform.js';10import { combinedDisposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';11import { LinkedList } from '../../../base/common/linkedList.js';1213export interface IKeybindingItem {14keybinding: Keybinding | null;15command: string | null;16commandArgs?: any;17when: ContextKeyExpression | null | undefined;18weight1: number;19weight2: number;20extensionId: string | null;21isBuiltinExtension: boolean;22}2324export interface IKeybindings {25primary?: number;26secondary?: number[];27win?: {28primary: number;29secondary?: number[];30};31linux?: {32primary: number;33secondary?: number[];34};35mac?: {36primary: number;37secondary?: number[];38};39}4041export interface IKeybindingRule extends IKeybindings {42id: string;43weight: number;44args?: any;45/**46* Keybinding is disabled if expression returns false.47*/48when?: ContextKeyExpression | null | undefined;49}5051export interface IExtensionKeybindingRule {52keybinding: Keybinding | null;53id: string;54args?: any;55weight: number;56when: ContextKeyExpression | undefined;57extensionId?: string;58isBuiltinExtension?: boolean;59}6061export const enum KeybindingWeight {62EditorCore = 0,63EditorContrib = 100,64WorkbenchContrib = 200,65BuiltinExtension = 300,66ExternalExtension = 40067}6869export interface ICommandAndKeybindingRule extends IKeybindingRule {70handler: ICommandHandler;71metadata?: ICommandMetadata | null;72}7374export interface IKeybindingsRegistry {75registerKeybindingRule(rule: IKeybindingRule): IDisposable;76setExtensionKeybindings(rules: IExtensionKeybindingRule[]): void;77registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): IDisposable;78getDefaultKeybindings(): IKeybindingItem[];79}8081/**82* Stores all built-in and extension-provided keybindings (but not ones that user defines themselves)83*/84class KeybindingsRegistryImpl implements IKeybindingsRegistry {8586private _coreKeybindings: LinkedList<IKeybindingItem>;87private _extensionKeybindings: IKeybindingItem[];88private _cachedMergedKeybindings: IKeybindingItem[] | null;8990constructor() {91this._coreKeybindings = new LinkedList();92this._extensionKeybindings = [];93this._cachedMergedKeybindings = null;94}9596/**97* Take current platform into account and reduce to primary & secondary.98*/99private static bindToCurrentPlatform(kb: IKeybindings): { primary?: number; secondary?: number[] } {100if (OS === OperatingSystem.Windows) {101if (kb && kb.win) {102return kb.win;103}104} else if (OS === OperatingSystem.Macintosh) {105if (kb && kb.mac) {106return kb.mac;107}108} else {109if (kb && kb.linux) {110return kb.linux;111}112}113114return kb;115}116117public registerKeybindingRule(rule: IKeybindingRule): IDisposable {118const actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);119const result = new DisposableStore();120121if (actualKb && actualKb.primary) {122const kk = decodeKeybinding(actualKb.primary, OS);123if (kk) {124result.add(this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, 0, rule.when));125}126}127128if (actualKb && Array.isArray(actualKb.secondary)) {129for (let i = 0, len = actualKb.secondary.length; i < len; i++) {130const k = actualKb.secondary[i];131const kk = decodeKeybinding(k, OS);132if (kk) {133result.add(this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, -i - 1, rule.when));134}135}136}137return result;138}139140public setExtensionKeybindings(rules: IExtensionKeybindingRule[]): void {141const result: IKeybindingItem[] = [];142let keybindingsLen = 0;143for (const rule of rules) {144if (rule.keybinding) {145result[keybindingsLen++] = {146keybinding: rule.keybinding,147command: rule.id,148commandArgs: rule.args,149when: rule.when,150weight1: rule.weight,151weight2: 0,152extensionId: rule.extensionId || null,153isBuiltinExtension: rule.isBuiltinExtension || false154};155}156}157158this._extensionKeybindings = result;159this._cachedMergedKeybindings = null;160}161162public registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): IDisposable {163return combinedDisposable(164this.registerKeybindingRule(desc),165CommandsRegistry.registerCommand(desc)166);167}168169private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpression | null | undefined): IDisposable {170const remove = this._coreKeybindings.push({171keybinding: keybinding,172command: commandId,173commandArgs: commandArgs,174when: when,175weight1: weight1,176weight2: weight2,177extensionId: null,178isBuiltinExtension: false179});180this._cachedMergedKeybindings = null;181182return toDisposable(() => {183remove();184this._cachedMergedKeybindings = null;185});186}187188public getDefaultKeybindings(): IKeybindingItem[] {189if (!this._cachedMergedKeybindings) {190this._cachedMergedKeybindings = Array.from(this._coreKeybindings).concat(this._extensionKeybindings);191this._cachedMergedKeybindings.sort(sorter);192}193return this._cachedMergedKeybindings.slice(0);194}195}196export const KeybindingsRegistry: IKeybindingsRegistry = new KeybindingsRegistryImpl();197198// Define extension point ids199export const Extensions = {200EditorModes: 'platform.keybindingsRegistry'201};202Registry.add(Extensions.EditorModes, KeybindingsRegistry);203204function sorter(a: IKeybindingItem, b: IKeybindingItem): number {205if (a.weight1 !== b.weight1) {206return a.weight1 - b.weight1;207}208if (a.command && b.command) {209if (a.command < b.command) {210return -1;211}212if (a.command > b.command) {213return 1;214}215}216return a.weight2 - b.weight2;217}218219220