feat: 添加vscode编辑器
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IntervalTimer, TimeoutTimer } from '../../../base/common/async.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import * as nls from '../../../nls.js';
|
||||
const HIGH_FREQ_COMMANDS = /^(cursor|delete)/;
|
||||
export class AbstractKeybindingService extends Disposable {
|
||||
constructor(_contextKeyService, _commandService, _telemetryService, _notificationService, _logService) {
|
||||
super();
|
||||
this._contextKeyService = _contextKeyService;
|
||||
this._commandService = _commandService;
|
||||
this._telemetryService = _telemetryService;
|
||||
this._notificationService = _notificationService;
|
||||
this._logService = _logService;
|
||||
this._onDidUpdateKeybindings = this._register(new Emitter());
|
||||
this._currentChord = null;
|
||||
this._currentChordChecker = new IntervalTimer();
|
||||
this._currentChordStatusMessage = null;
|
||||
this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY;
|
||||
this._currentSingleModifier = null;
|
||||
this._currentSingleModifierClearTimeout = new TimeoutTimer();
|
||||
this._logging = false;
|
||||
}
|
||||
get onDidUpdateKeybindings() {
|
||||
return this._onDidUpdateKeybindings ? this._onDidUpdateKeybindings.event : Event.None; // Sinon stubbing walks properties on prototype
|
||||
}
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
_log(str) {
|
||||
if (this._logging) {
|
||||
this._logService.info(`[KeybindingService]: ${str}`);
|
||||
}
|
||||
}
|
||||
getKeybindings() {
|
||||
return this._getResolver().getKeybindings();
|
||||
}
|
||||
lookupKeybinding(commandId, context) {
|
||||
const result = this._getResolver().lookupPrimaryKeybinding(commandId, context || this._contextKeyService);
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
return result.resolvedKeybinding;
|
||||
}
|
||||
dispatchEvent(e, target) {
|
||||
return this._dispatch(e, target);
|
||||
}
|
||||
softDispatch(e, target) {
|
||||
this._log(`/ Soft dispatching keyboard event`);
|
||||
const keybinding = this.resolveKeyboardEvent(e);
|
||||
if (keybinding.isChord()) {
|
||||
console.warn('Unexpected keyboard event mapped to a chord');
|
||||
return null;
|
||||
}
|
||||
const [firstPart,] = keybinding.getDispatchParts();
|
||||
if (firstPart === null) {
|
||||
// cannot be dispatched, probably only modifier keys
|
||||
this._log(`\\ Keyboard event cannot be dispatched`);
|
||||
return null;
|
||||
}
|
||||
const contextValue = this._contextKeyService.getContext(target);
|
||||
const currentChord = this._currentChord ? this._currentChord.keypress : null;
|
||||
return this._getResolver().resolve(contextValue, currentChord, firstPart);
|
||||
}
|
||||
_enterChordMode(firstPart, keypressLabel) {
|
||||
this._currentChord = {
|
||||
keypress: firstPart,
|
||||
label: keypressLabel
|
||||
};
|
||||
this._currentChordStatusMessage = this._notificationService.status(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel));
|
||||
const chordEnterTime = Date.now();
|
||||
this._currentChordChecker.cancelAndSet(() => {
|
||||
if (!this._documentHasFocus()) {
|
||||
// Focus has been lost => leave chord mode
|
||||
this._leaveChordMode();
|
||||
return;
|
||||
}
|
||||
if (Date.now() - chordEnterTime > 5000) {
|
||||
// 5 seconds elapsed => leave chord mode
|
||||
this._leaveChordMode();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
_leaveChordMode() {
|
||||
if (this._currentChordStatusMessage) {
|
||||
this._currentChordStatusMessage.dispose();
|
||||
this._currentChordStatusMessage = null;
|
||||
}
|
||||
this._currentChordChecker.cancel();
|
||||
this._currentChord = null;
|
||||
}
|
||||
_dispatch(e, target) {
|
||||
return this._doDispatch(this.resolveKeyboardEvent(e), target, /*isSingleModiferChord*/ false);
|
||||
}
|
||||
_singleModifierDispatch(e, target) {
|
||||
const keybinding = this.resolveKeyboardEvent(e);
|
||||
const [singleModifier,] = keybinding.getSingleModifierDispatchParts();
|
||||
if (singleModifier) {
|
||||
if (this._ignoreSingleModifiers.has(singleModifier)) {
|
||||
this._log(`+ Ignoring single modifier ${singleModifier} due to it being pressed together with other keys.`);
|
||||
this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY;
|
||||
this._currentSingleModifierClearTimeout.cancel();
|
||||
this._currentSingleModifier = null;
|
||||
return false;
|
||||
}
|
||||
this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY;
|
||||
if (this._currentSingleModifier === null) {
|
||||
// we have a valid `singleModifier`, store it for the next keyup, but clear it in 300ms
|
||||
this._log(`+ Storing single modifier for possible chord ${singleModifier}.`);
|
||||
this._currentSingleModifier = singleModifier;
|
||||
this._currentSingleModifierClearTimeout.cancelAndSet(() => {
|
||||
this._log(`+ Clearing single modifier due to 300ms elapsed.`);
|
||||
this._currentSingleModifier = null;
|
||||
}, 300);
|
||||
return false;
|
||||
}
|
||||
if (singleModifier === this._currentSingleModifier) {
|
||||
// bingo!
|
||||
this._log(`/ Dispatching single modifier chord ${singleModifier} ${singleModifier}`);
|
||||
this._currentSingleModifierClearTimeout.cancel();
|
||||
this._currentSingleModifier = null;
|
||||
return this._doDispatch(keybinding, target, /*isSingleModiferChord*/ true);
|
||||
}
|
||||
this._log(`+ Clearing single modifier due to modifier mismatch: ${this._currentSingleModifier} ${singleModifier}`);
|
||||
this._currentSingleModifierClearTimeout.cancel();
|
||||
this._currentSingleModifier = null;
|
||||
return false;
|
||||
}
|
||||
// When pressing a modifier and holding it pressed with any other modifier or key combination,
|
||||
// the pressed modifiers should no longer be considered for single modifier dispatch.
|
||||
const [firstPart,] = keybinding.getParts();
|
||||
this._ignoreSingleModifiers = new KeybindingModifierSet(firstPart);
|
||||
if (this._currentSingleModifier !== null) {
|
||||
this._log(`+ Clearing single modifier due to other key up.`);
|
||||
}
|
||||
this._currentSingleModifierClearTimeout.cancel();
|
||||
this._currentSingleModifier = null;
|
||||
return false;
|
||||
}
|
||||
_doDispatch(keybinding, target, isSingleModiferChord = false) {
|
||||
let shouldPreventDefault = false;
|
||||
if (keybinding.isChord()) {
|
||||
console.warn('Unexpected keyboard event mapped to a chord');
|
||||
return false;
|
||||
}
|
||||
let firstPart = null; // the first keybinding i.e. Ctrl+K
|
||||
let currentChord = null; // the "second" keybinding i.e. Ctrl+K "Ctrl+D"
|
||||
if (isSingleModiferChord) {
|
||||
const [dispatchKeyname,] = keybinding.getSingleModifierDispatchParts();
|
||||
firstPart = dispatchKeyname;
|
||||
currentChord = dispatchKeyname;
|
||||
}
|
||||
else {
|
||||
[firstPart,] = keybinding.getDispatchParts();
|
||||
currentChord = this._currentChord ? this._currentChord.keypress : null;
|
||||
}
|
||||
if (firstPart === null) {
|
||||
this._log(`\\ Keyboard event cannot be dispatched in keydown phase.`);
|
||||
// cannot be dispatched, probably only modifier keys
|
||||
return shouldPreventDefault;
|
||||
}
|
||||
const contextValue = this._contextKeyService.getContext(target);
|
||||
const keypressLabel = keybinding.getLabel();
|
||||
const resolveResult = this._getResolver().resolve(contextValue, currentChord, firstPart);
|
||||
this._logService.trace('KeybindingService#dispatch', keypressLabel, resolveResult === null || resolveResult === void 0 ? void 0 : resolveResult.commandId);
|
||||
if (resolveResult && resolveResult.enterChord) {
|
||||
shouldPreventDefault = true;
|
||||
this._enterChordMode(firstPart, keypressLabel);
|
||||
this._log(`+ Entering chord mode...`);
|
||||
return shouldPreventDefault;
|
||||
}
|
||||
if (this._currentChord) {
|
||||
if (!resolveResult || !resolveResult.commandId) {
|
||||
this._log(`+ Leaving chord mode: Nothing bound to "${this._currentChord.label} ${keypressLabel}".`);
|
||||
this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", this._currentChord.label, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ });
|
||||
shouldPreventDefault = true;
|
||||
}
|
||||
}
|
||||
this._leaveChordMode();
|
||||
if (resolveResult && resolveResult.commandId) {
|
||||
if (!resolveResult.bubble) {
|
||||
shouldPreventDefault = true;
|
||||
}
|
||||
this._log(`+ Invoking command ${resolveResult.commandId}.`);
|
||||
if (typeof resolveResult.commandArgs === 'undefined') {
|
||||
this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err));
|
||||
}
|
||||
else {
|
||||
this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err));
|
||||
}
|
||||
if (!HIGH_FREQ_COMMANDS.test(resolveResult.commandId)) {
|
||||
this._telemetryService.publicLog2('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding' });
|
||||
}
|
||||
}
|
||||
return shouldPreventDefault;
|
||||
}
|
||||
mightProducePrintableCharacter(event) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
// ignore ctrl/cmd-combination but not shift/alt-combinatios
|
||||
return false;
|
||||
}
|
||||
// weak check for certain ranges. this is properly implemented in a subclass
|
||||
// with access to the KeyboardMapperFactory.
|
||||
if ((event.keyCode >= 31 /* KeyCode.KeyA */ && event.keyCode <= 56 /* KeyCode.KeyZ */)
|
||||
|| (event.keyCode >= 21 /* KeyCode.Digit0 */ && event.keyCode <= 30 /* KeyCode.Digit9 */)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
class KeybindingModifierSet {
|
||||
constructor(source) {
|
||||
this._ctrlKey = source ? source.ctrlKey : false;
|
||||
this._shiftKey = source ? source.shiftKey : false;
|
||||
this._altKey = source ? source.altKey : false;
|
||||
this._metaKey = source ? source.metaKey : false;
|
||||
}
|
||||
has(modifier) {
|
||||
switch (modifier) {
|
||||
case 'ctrl': return this._ctrlKey;
|
||||
case 'shift': return this._shiftKey;
|
||||
case 'alt': return this._altKey;
|
||||
case 'meta': return this._metaKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
KeybindingModifierSet.EMPTY = new KeybindingModifierSet(null);
|
||||
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { illegalArgument } from '../../../base/common/errors.js';
|
||||
import { AriaLabelProvider, ElectronAcceleratorLabelProvider, UILabelProvider } from '../../../base/common/keybindingLabels.js';
|
||||
import { ResolvedKeybinding, ResolvedKeybindingPart } from '../../../base/common/keybindings.js';
|
||||
export class BaseResolvedKeybinding extends ResolvedKeybinding {
|
||||
constructor(os, parts) {
|
||||
super();
|
||||
if (parts.length === 0) {
|
||||
throw illegalArgument(`parts`);
|
||||
}
|
||||
this._os = os;
|
||||
this._parts = parts;
|
||||
}
|
||||
getLabel() {
|
||||
return UILabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getLabel(keybinding));
|
||||
}
|
||||
getAriaLabel() {
|
||||
return AriaLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getAriaLabel(keybinding));
|
||||
}
|
||||
getElectronAccelerator() {
|
||||
if (this._parts.length > 1) {
|
||||
// [Electron Accelerators] Electron cannot handle chords
|
||||
return null;
|
||||
}
|
||||
if (this._parts[0].isDuplicateModifierCase()) {
|
||||
// [Electron Accelerators] Electron cannot handle modifier only keybindings
|
||||
// e.g. "shift shift"
|
||||
return null;
|
||||
}
|
||||
return ElectronAcceleratorLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getElectronAccelerator(keybinding));
|
||||
}
|
||||
isChord() {
|
||||
return (this._parts.length > 1);
|
||||
}
|
||||
getParts() {
|
||||
return this._parts.map((keybinding) => this._getPart(keybinding));
|
||||
}
|
||||
_getPart(keybinding) {
|
||||
return new ResolvedKeybindingPart(keybinding.ctrlKey, keybinding.shiftKey, keybinding.altKey, keybinding.metaKey, this._getLabel(keybinding), this._getAriaLabel(keybinding));
|
||||
}
|
||||
getDispatchParts() {
|
||||
return this._parts.map((keybinding) => this._getDispatchPart(keybinding));
|
||||
}
|
||||
getSingleModifierDispatchParts() {
|
||||
return this._parts.map((keybinding) => this._getSingleModifierDispatchPart(keybinding));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
export const IKeybindingService = createDecorator('keybindingService');
|
||||
@@ -0,0 +1,278 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { implies, expressionsAreEqualWithConstantSubstitution } from '../../contextkey/common/contextkey.js';
|
||||
export class KeybindingResolver {
|
||||
constructor(defaultKeybindings, overrides, log) {
|
||||
this._log = log;
|
||||
this._defaultKeybindings = defaultKeybindings;
|
||||
this._defaultBoundCommands = new Map();
|
||||
for (const defaultKeybinding of defaultKeybindings) {
|
||||
const command = defaultKeybinding.command;
|
||||
if (command && command.charAt(0) !== '-') {
|
||||
this._defaultBoundCommands.set(command, true);
|
||||
}
|
||||
}
|
||||
this._map = new Map();
|
||||
this._lookupMap = new Map();
|
||||
this._keybindings = KeybindingResolver.handleRemovals([].concat(defaultKeybindings).concat(overrides));
|
||||
for (let i = 0, len = this._keybindings.length; i < len; i++) {
|
||||
const k = this._keybindings[i];
|
||||
if (k.keypressParts.length === 0) {
|
||||
// unbound
|
||||
continue;
|
||||
}
|
||||
if (k.when && k.when.type === 0 /* ContextKeyExprType.False */) {
|
||||
// when condition is false
|
||||
continue;
|
||||
}
|
||||
// TODO@chords
|
||||
this._addKeyPress(k.keypressParts[0], k);
|
||||
}
|
||||
}
|
||||
static _isTargetedForRemoval(defaultKb, keypressFirstPart, keypressChordPart, when) {
|
||||
// TODO@chords
|
||||
if (keypressFirstPart && defaultKb.keypressParts[0] !== keypressFirstPart) {
|
||||
return false;
|
||||
}
|
||||
// TODO@chords
|
||||
if (keypressChordPart && defaultKb.keypressParts[1] !== keypressChordPart) {
|
||||
return false;
|
||||
}
|
||||
if (when) {
|
||||
if (!defaultKb.when) {
|
||||
return false;
|
||||
}
|
||||
if (!expressionsAreEqualWithConstantSubstitution(when, defaultKb.when)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Looks for rules containing "-commandId" and removes them.
|
||||
*/
|
||||
static handleRemovals(rules) {
|
||||
// Do a first pass and construct a hash-map for removals
|
||||
const removals = new Map();
|
||||
for (let i = 0, len = rules.length; i < len; i++) {
|
||||
const rule = rules[i];
|
||||
if (rule.command && rule.command.charAt(0) === '-') {
|
||||
const command = rule.command.substring(1);
|
||||
if (!removals.has(command)) {
|
||||
removals.set(command, [rule]);
|
||||
}
|
||||
else {
|
||||
removals.get(command).push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removals.size === 0) {
|
||||
// There are no removals
|
||||
return rules;
|
||||
}
|
||||
// Do a second pass and keep only non-removed keybindings
|
||||
const result = [];
|
||||
for (let i = 0, len = rules.length; i < len; i++) {
|
||||
const rule = rules[i];
|
||||
if (!rule.command || rule.command.length === 0) {
|
||||
result.push(rule);
|
||||
continue;
|
||||
}
|
||||
if (rule.command.charAt(0) === '-') {
|
||||
continue;
|
||||
}
|
||||
const commandRemovals = removals.get(rule.command);
|
||||
if (!commandRemovals || !rule.isDefault) {
|
||||
result.push(rule);
|
||||
continue;
|
||||
}
|
||||
let isRemoved = false;
|
||||
for (const commandRemoval of commandRemovals) {
|
||||
// TODO@chords
|
||||
const keypressFirstPart = commandRemoval.keypressParts[0];
|
||||
const keypressChordPart = commandRemoval.keypressParts[1];
|
||||
const when = commandRemoval.when;
|
||||
if (this._isTargetedForRemoval(rule, keypressFirstPart, keypressChordPart, when)) {
|
||||
isRemoved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isRemoved) {
|
||||
result.push(rule);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
_addKeyPress(keypress, item) {
|
||||
const conflicts = this._map.get(keypress);
|
||||
if (typeof conflicts === 'undefined') {
|
||||
// There is no conflict so far
|
||||
this._map.set(keypress, [item]);
|
||||
this._addToLookupMap(item);
|
||||
return;
|
||||
}
|
||||
for (let i = conflicts.length - 1; i >= 0; i--) {
|
||||
const conflict = conflicts[i];
|
||||
if (conflict.command === item.command) {
|
||||
continue;
|
||||
}
|
||||
const conflictIsChord = (conflict.keypressParts.length > 1);
|
||||
const itemIsChord = (item.keypressParts.length > 1);
|
||||
// TODO@chords
|
||||
if (conflictIsChord && itemIsChord && conflict.keypressParts[1] !== item.keypressParts[1]) {
|
||||
// The conflict only shares the chord start with this command
|
||||
continue;
|
||||
}
|
||||
if (KeybindingResolver.whenIsEntirelyIncluded(conflict.when, item.when)) {
|
||||
// `item` completely overwrites `conflict`
|
||||
// Remove conflict from the lookupMap
|
||||
this._removeFromLookupMap(conflict);
|
||||
}
|
||||
}
|
||||
conflicts.push(item);
|
||||
this._addToLookupMap(item);
|
||||
}
|
||||
_addToLookupMap(item) {
|
||||
if (!item.command) {
|
||||
return;
|
||||
}
|
||||
let arr = this._lookupMap.get(item.command);
|
||||
if (typeof arr === 'undefined') {
|
||||
arr = [item];
|
||||
this._lookupMap.set(item.command, arr);
|
||||
}
|
||||
else {
|
||||
arr.push(item);
|
||||
}
|
||||
}
|
||||
_removeFromLookupMap(item) {
|
||||
if (!item.command) {
|
||||
return;
|
||||
}
|
||||
const arr = this._lookupMap.get(item.command);
|
||||
if (typeof arr === 'undefined') {
|
||||
return;
|
||||
}
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
if (arr[i] === item) {
|
||||
arr.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if it is provable `a` implies `b`.
|
||||
*/
|
||||
static whenIsEntirelyIncluded(a, b) {
|
||||
if (!b || b.type === 1 /* ContextKeyExprType.True */) {
|
||||
return true;
|
||||
}
|
||||
if (!a || a.type === 1 /* ContextKeyExprType.True */) {
|
||||
return false;
|
||||
}
|
||||
return implies(a, b);
|
||||
}
|
||||
getKeybindings() {
|
||||
return this._keybindings;
|
||||
}
|
||||
lookupPrimaryKeybinding(commandId, context) {
|
||||
const items = this._lookupMap.get(commandId);
|
||||
if (typeof items === 'undefined' || items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (items.length === 1) {
|
||||
return items[0];
|
||||
}
|
||||
for (let i = items.length - 1; i >= 0; i--) {
|
||||
const item = items[i];
|
||||
if (context.contextMatchesRules(item.when)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return items[items.length - 1];
|
||||
}
|
||||
resolve(context, currentChord, keypress) {
|
||||
this._log(`| Resolving ${keypress}${currentChord ? ` chorded from ${currentChord}` : ``}`);
|
||||
let lookupMap = null;
|
||||
if (currentChord !== null) {
|
||||
// Fetch all chord bindings for `currentChord`
|
||||
const candidates = this._map.get(currentChord);
|
||||
if (typeof candidates === 'undefined') {
|
||||
// No chords starting with `currentChord`
|
||||
this._log(`\\ No keybinding entries.`);
|
||||
return null;
|
||||
}
|
||||
lookupMap = [];
|
||||
for (let i = 0, len = candidates.length; i < len; i++) {
|
||||
const candidate = candidates[i];
|
||||
// TODO@chords
|
||||
if (candidate.keypressParts[1] === keypress) {
|
||||
lookupMap.push(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const candidates = this._map.get(keypress);
|
||||
if (typeof candidates === 'undefined') {
|
||||
// No bindings with `keypress`
|
||||
this._log(`\\ No keybinding entries.`);
|
||||
return null;
|
||||
}
|
||||
lookupMap = candidates;
|
||||
}
|
||||
const result = this._findCommand(context, lookupMap);
|
||||
if (!result) {
|
||||
this._log(`\\ From ${lookupMap.length} keybinding entries, no when clauses matched the context.`);
|
||||
return null;
|
||||
}
|
||||
// TODO@chords
|
||||
if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) {
|
||||
this._log(`\\ From ${lookupMap.length} keybinding entries, matched chord, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
|
||||
return {
|
||||
enterChord: true,
|
||||
leaveChord: false,
|
||||
commandId: null,
|
||||
commandArgs: null,
|
||||
bubble: false
|
||||
};
|
||||
}
|
||||
this._log(`\\ From ${lookupMap.length} keybinding entries, matched ${result.command}, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
|
||||
return {
|
||||
enterChord: false,
|
||||
leaveChord: result.keypressParts.length > 1,
|
||||
commandId: result.command,
|
||||
commandArgs: result.commandArgs,
|
||||
bubble: result.bubble
|
||||
};
|
||||
}
|
||||
_findCommand(context, matches) {
|
||||
for (let i = matches.length - 1; i >= 0; i--) {
|
||||
const k = matches[i];
|
||||
if (!KeybindingResolver._contextMatchesRules(context, k.when)) {
|
||||
continue;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static _contextMatchesRules(context, rules) {
|
||||
if (!rules) {
|
||||
return true;
|
||||
}
|
||||
return rules.evaluate(context);
|
||||
}
|
||||
}
|
||||
function printWhenExplanation(when) {
|
||||
if (!when) {
|
||||
return `no when condition`;
|
||||
}
|
||||
return `${when.serialize()}`;
|
||||
}
|
||||
function printSourceExplanation(kb) {
|
||||
return (kb.extensionId
|
||||
? (kb.isBuiltinExtension ? `built-in extension ${kb.extensionId}` : `user extension ${kb.extensionId}`)
|
||||
: (kb.isDefault ? `built-in` : `user`));
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { createKeybinding } from '../../../base/common/keybindings.js';
|
||||
import { OS } from '../../../base/common/platform.js';
|
||||
import { CommandsRegistry } from '../../commands/common/commands.js';
|
||||
import { Registry } from '../../registry/common/platform.js';
|
||||
class KeybindingsRegistryImpl {
|
||||
constructor() {
|
||||
this._coreKeybindings = [];
|
||||
this._extensionKeybindings = [];
|
||||
this._cachedMergedKeybindings = null;
|
||||
}
|
||||
/**
|
||||
* Take current platform into account and reduce to primary & secondary.
|
||||
*/
|
||||
static bindToCurrentPlatform(kb) {
|
||||
if (OS === 1 /* OperatingSystem.Windows */) {
|
||||
if (kb && kb.win) {
|
||||
return kb.win;
|
||||
}
|
||||
}
|
||||
else if (OS === 2 /* OperatingSystem.Macintosh */) {
|
||||
if (kb && kb.mac) {
|
||||
return kb.mac;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (kb && kb.linux) {
|
||||
return kb.linux;
|
||||
}
|
||||
}
|
||||
return kb;
|
||||
}
|
||||
registerKeybindingRule(rule) {
|
||||
const actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);
|
||||
if (actualKb && actualKb.primary) {
|
||||
const kk = createKeybinding(actualKb.primary, OS);
|
||||
if (kk) {
|
||||
this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, 0, rule.when);
|
||||
}
|
||||
}
|
||||
if (actualKb && Array.isArray(actualKb.secondary)) {
|
||||
for (let i = 0, len = actualKb.secondary.length; i < len; i++) {
|
||||
const k = actualKb.secondary[i];
|
||||
const kk = createKeybinding(k, OS);
|
||||
if (kk) {
|
||||
this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, -i - 1, rule.when);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
registerCommandAndKeybindingRule(desc) {
|
||||
this.registerKeybindingRule(desc);
|
||||
CommandsRegistry.registerCommand(desc);
|
||||
}
|
||||
static _mightProduceChar(keyCode) {
|
||||
if (keyCode >= 21 /* KeyCode.Digit0 */ && keyCode <= 30 /* KeyCode.Digit9 */) {
|
||||
return true;
|
||||
}
|
||||
if (keyCode >= 31 /* KeyCode.KeyA */ && keyCode <= 56 /* KeyCode.KeyZ */) {
|
||||
return true;
|
||||
}
|
||||
return (keyCode === 80 /* KeyCode.Semicolon */
|
||||
|| keyCode === 81 /* KeyCode.Equal */
|
||||
|| keyCode === 82 /* KeyCode.Comma */
|
||||
|| keyCode === 83 /* KeyCode.Minus */
|
||||
|| keyCode === 84 /* KeyCode.Period */
|
||||
|| keyCode === 85 /* KeyCode.Slash */
|
||||
|| keyCode === 86 /* KeyCode.Backquote */
|
||||
|| keyCode === 110 /* KeyCode.ABNT_C1 */
|
||||
|| keyCode === 111 /* KeyCode.ABNT_C2 */
|
||||
|| keyCode === 87 /* KeyCode.BracketLeft */
|
||||
|| keyCode === 88 /* KeyCode.Backslash */
|
||||
|| keyCode === 89 /* KeyCode.BracketRight */
|
||||
|| keyCode === 90 /* KeyCode.Quote */
|
||||
|| keyCode === 91 /* KeyCode.OEM_8 */
|
||||
|| keyCode === 92 /* KeyCode.IntlBackslash */);
|
||||
}
|
||||
_assertNoCtrlAlt(keybinding, commandId) {
|
||||
if (keybinding.ctrlKey && keybinding.altKey && !keybinding.metaKey) {
|
||||
if (KeybindingsRegistryImpl._mightProduceChar(keybinding.keyCode)) {
|
||||
console.warn('Ctrl+Alt+ keybindings should not be used by default under Windows. Offender: ', keybinding, ' for ', commandId);
|
||||
}
|
||||
}
|
||||
}
|
||||
_registerDefaultKeybinding(keybinding, commandId, commandArgs, weight1, weight2, when) {
|
||||
if (OS === 1 /* OperatingSystem.Windows */) {
|
||||
this._assertNoCtrlAlt(keybinding.parts[0], commandId);
|
||||
}
|
||||
this._coreKeybindings.push({
|
||||
keybinding: keybinding.parts,
|
||||
command: commandId,
|
||||
commandArgs: commandArgs,
|
||||
when: when,
|
||||
weight1: weight1,
|
||||
weight2: weight2,
|
||||
extensionId: null,
|
||||
isBuiltinExtension: false
|
||||
});
|
||||
this._cachedMergedKeybindings = null;
|
||||
}
|
||||
getDefaultKeybindings() {
|
||||
if (!this._cachedMergedKeybindings) {
|
||||
this._cachedMergedKeybindings = [].concat(this._coreKeybindings).concat(this._extensionKeybindings);
|
||||
this._cachedMergedKeybindings.sort(sorter);
|
||||
}
|
||||
return this._cachedMergedKeybindings.slice(0);
|
||||
}
|
||||
}
|
||||
export const KeybindingsRegistry = new KeybindingsRegistryImpl();
|
||||
// Define extension point ids
|
||||
export const Extensions = {
|
||||
EditorModes: 'platform.keybindingsRegistry'
|
||||
};
|
||||
Registry.add(Extensions.EditorModes, KeybindingsRegistry);
|
||||
function sorter(a, b) {
|
||||
if (a.weight1 !== b.weight1) {
|
||||
return a.weight1 - b.weight1;
|
||||
}
|
||||
if (a.command < b.command) {
|
||||
return -1;
|
||||
}
|
||||
if (a.command > b.command) {
|
||||
return 1;
|
||||
}
|
||||
return a.weight2 - b.weight2;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export class ResolvedKeybindingItem {
|
||||
constructor(resolvedKeybinding, command, commandArgs, when, isDefault, extensionId, isBuiltinExtension) {
|
||||
this._resolvedKeybindingItemBrand = undefined;
|
||||
this.resolvedKeybinding = resolvedKeybinding;
|
||||
this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : [];
|
||||
if (resolvedKeybinding && this.keypressParts.length === 0) {
|
||||
// handle possible single modifier chord keybindings
|
||||
this.keypressParts = removeElementsAfterNulls(resolvedKeybinding.getSingleModifierDispatchParts());
|
||||
}
|
||||
this.bubble = (command ? command.charCodeAt(0) === 94 /* CharCode.Caret */ : false);
|
||||
this.command = this.bubble ? command.substr(1) : command;
|
||||
this.commandArgs = commandArgs;
|
||||
this.when = when;
|
||||
this.isDefault = isDefault;
|
||||
this.extensionId = extensionId;
|
||||
this.isBuiltinExtension = isBuiltinExtension;
|
||||
}
|
||||
}
|
||||
export function removeElementsAfterNulls(arr) {
|
||||
const result = [];
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
const element = arr[i];
|
||||
if (!element) {
|
||||
// stop processing at first encountered null
|
||||
return result;
|
||||
}
|
||||
result.push(element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { KeyCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from '../../../base/common/keyCodes.js';
|
||||
import { ChordKeybinding, SimpleKeybinding } from '../../../base/common/keybindings.js';
|
||||
import { BaseResolvedKeybinding } from './baseResolvedKeybinding.js';
|
||||
import { removeElementsAfterNulls } from './resolvedKeybindingItem.js';
|
||||
/**
|
||||
* Do not instantiate. Use KeybindingService to get a ResolvedKeybinding seeded with information about the current kb layout.
|
||||
*/
|
||||
export class USLayoutResolvedKeybinding extends BaseResolvedKeybinding {
|
||||
constructor(actual, os) {
|
||||
super(os, actual.parts);
|
||||
}
|
||||
_keyCodeToUILabel(keyCode) {
|
||||
if (this._os === 2 /* OperatingSystem.Macintosh */) {
|
||||
switch (keyCode) {
|
||||
case 15 /* KeyCode.LeftArrow */:
|
||||
return '←';
|
||||
case 16 /* KeyCode.UpArrow */:
|
||||
return '↑';
|
||||
case 17 /* KeyCode.RightArrow */:
|
||||
return '→';
|
||||
case 18 /* KeyCode.DownArrow */:
|
||||
return '↓';
|
||||
}
|
||||
}
|
||||
return KeyCodeUtils.toString(keyCode);
|
||||
}
|
||||
_getLabel(keybinding) {
|
||||
if (keybinding.isDuplicateModifierCase()) {
|
||||
return '';
|
||||
}
|
||||
return this._keyCodeToUILabel(keybinding.keyCode);
|
||||
}
|
||||
_getAriaLabel(keybinding) {
|
||||
if (keybinding.isDuplicateModifierCase()) {
|
||||
return '';
|
||||
}
|
||||
return KeyCodeUtils.toString(keybinding.keyCode);
|
||||
}
|
||||
_getElectronAccelerator(keybinding) {
|
||||
return KeyCodeUtils.toElectronAccelerator(keybinding.keyCode);
|
||||
}
|
||||
_getDispatchPart(keybinding) {
|
||||
return USLayoutResolvedKeybinding.getDispatchStr(keybinding);
|
||||
}
|
||||
static getDispatchStr(keybinding) {
|
||||
if (keybinding.isModifierKey()) {
|
||||
return null;
|
||||
}
|
||||
let result = '';
|
||||
if (keybinding.ctrlKey) {
|
||||
result += 'ctrl+';
|
||||
}
|
||||
if (keybinding.shiftKey) {
|
||||
result += 'shift+';
|
||||
}
|
||||
if (keybinding.altKey) {
|
||||
result += 'alt+';
|
||||
}
|
||||
if (keybinding.metaKey) {
|
||||
result += 'meta+';
|
||||
}
|
||||
result += KeyCodeUtils.toString(keybinding.keyCode);
|
||||
return result;
|
||||
}
|
||||
_getSingleModifierDispatchPart(keybinding) {
|
||||
if (keybinding.keyCode === 5 /* KeyCode.Ctrl */ && !keybinding.shiftKey && !keybinding.altKey && !keybinding.metaKey) {
|
||||
return 'ctrl';
|
||||
}
|
||||
if (keybinding.keyCode === 4 /* KeyCode.Shift */ && !keybinding.ctrlKey && !keybinding.altKey && !keybinding.metaKey) {
|
||||
return 'shift';
|
||||
}
|
||||
if (keybinding.keyCode === 6 /* KeyCode.Alt */ && !keybinding.ctrlKey && !keybinding.shiftKey && !keybinding.metaKey) {
|
||||
return 'alt';
|
||||
}
|
||||
if (keybinding.keyCode === 57 /* KeyCode.Meta */ && !keybinding.ctrlKey && !keybinding.shiftKey && !keybinding.altKey) {
|
||||
return 'meta';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* *NOTE*: Check return value for `KeyCode.Unknown`.
|
||||
*/
|
||||
static _scanCodeToKeyCode(scanCode) {
|
||||
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode];
|
||||
if (immutableKeyCode !== -1 /* KeyCode.DependsOnKbLayout */) {
|
||||
return immutableKeyCode;
|
||||
}
|
||||
switch (scanCode) {
|
||||
case 10 /* ScanCode.KeyA */: return 31 /* KeyCode.KeyA */;
|
||||
case 11 /* ScanCode.KeyB */: return 32 /* KeyCode.KeyB */;
|
||||
case 12 /* ScanCode.KeyC */: return 33 /* KeyCode.KeyC */;
|
||||
case 13 /* ScanCode.KeyD */: return 34 /* KeyCode.KeyD */;
|
||||
case 14 /* ScanCode.KeyE */: return 35 /* KeyCode.KeyE */;
|
||||
case 15 /* ScanCode.KeyF */: return 36 /* KeyCode.KeyF */;
|
||||
case 16 /* ScanCode.KeyG */: return 37 /* KeyCode.KeyG */;
|
||||
case 17 /* ScanCode.KeyH */: return 38 /* KeyCode.KeyH */;
|
||||
case 18 /* ScanCode.KeyI */: return 39 /* KeyCode.KeyI */;
|
||||
case 19 /* ScanCode.KeyJ */: return 40 /* KeyCode.KeyJ */;
|
||||
case 20 /* ScanCode.KeyK */: return 41 /* KeyCode.KeyK */;
|
||||
case 21 /* ScanCode.KeyL */: return 42 /* KeyCode.KeyL */;
|
||||
case 22 /* ScanCode.KeyM */: return 43 /* KeyCode.KeyM */;
|
||||
case 23 /* ScanCode.KeyN */: return 44 /* KeyCode.KeyN */;
|
||||
case 24 /* ScanCode.KeyO */: return 45 /* KeyCode.KeyO */;
|
||||
case 25 /* ScanCode.KeyP */: return 46 /* KeyCode.KeyP */;
|
||||
case 26 /* ScanCode.KeyQ */: return 47 /* KeyCode.KeyQ */;
|
||||
case 27 /* ScanCode.KeyR */: return 48 /* KeyCode.KeyR */;
|
||||
case 28 /* ScanCode.KeyS */: return 49 /* KeyCode.KeyS */;
|
||||
case 29 /* ScanCode.KeyT */: return 50 /* KeyCode.KeyT */;
|
||||
case 30 /* ScanCode.KeyU */: return 51 /* KeyCode.KeyU */;
|
||||
case 31 /* ScanCode.KeyV */: return 52 /* KeyCode.KeyV */;
|
||||
case 32 /* ScanCode.KeyW */: return 53 /* KeyCode.KeyW */;
|
||||
case 33 /* ScanCode.KeyX */: return 54 /* KeyCode.KeyX */;
|
||||
case 34 /* ScanCode.KeyY */: return 55 /* KeyCode.KeyY */;
|
||||
case 35 /* ScanCode.KeyZ */: return 56 /* KeyCode.KeyZ */;
|
||||
case 36 /* ScanCode.Digit1 */: return 22 /* KeyCode.Digit1 */;
|
||||
case 37 /* ScanCode.Digit2 */: return 23 /* KeyCode.Digit2 */;
|
||||
case 38 /* ScanCode.Digit3 */: return 24 /* KeyCode.Digit3 */;
|
||||
case 39 /* ScanCode.Digit4 */: return 25 /* KeyCode.Digit4 */;
|
||||
case 40 /* ScanCode.Digit5 */: return 26 /* KeyCode.Digit5 */;
|
||||
case 41 /* ScanCode.Digit6 */: return 27 /* KeyCode.Digit6 */;
|
||||
case 42 /* ScanCode.Digit7 */: return 28 /* KeyCode.Digit7 */;
|
||||
case 43 /* ScanCode.Digit8 */: return 29 /* KeyCode.Digit8 */;
|
||||
case 44 /* ScanCode.Digit9 */: return 30 /* KeyCode.Digit9 */;
|
||||
case 45 /* ScanCode.Digit0 */: return 21 /* KeyCode.Digit0 */;
|
||||
case 51 /* ScanCode.Minus */: return 83 /* KeyCode.Minus */;
|
||||
case 52 /* ScanCode.Equal */: return 81 /* KeyCode.Equal */;
|
||||
case 53 /* ScanCode.BracketLeft */: return 87 /* KeyCode.BracketLeft */;
|
||||
case 54 /* ScanCode.BracketRight */: return 89 /* KeyCode.BracketRight */;
|
||||
case 55 /* ScanCode.Backslash */: return 88 /* KeyCode.Backslash */;
|
||||
case 56 /* ScanCode.IntlHash */: return 0 /* KeyCode.Unknown */; // missing
|
||||
case 57 /* ScanCode.Semicolon */: return 80 /* KeyCode.Semicolon */;
|
||||
case 58 /* ScanCode.Quote */: return 90 /* KeyCode.Quote */;
|
||||
case 59 /* ScanCode.Backquote */: return 86 /* KeyCode.Backquote */;
|
||||
case 60 /* ScanCode.Comma */: return 82 /* KeyCode.Comma */;
|
||||
case 61 /* ScanCode.Period */: return 84 /* KeyCode.Period */;
|
||||
case 62 /* ScanCode.Slash */: return 85 /* KeyCode.Slash */;
|
||||
case 106 /* ScanCode.IntlBackslash */: return 92 /* KeyCode.IntlBackslash */;
|
||||
}
|
||||
return 0 /* KeyCode.Unknown */;
|
||||
}
|
||||
static _resolveSimpleUserBinding(binding) {
|
||||
if (!binding) {
|
||||
return null;
|
||||
}
|
||||
if (binding instanceof SimpleKeybinding) {
|
||||
return binding;
|
||||
}
|
||||
const keyCode = this._scanCodeToKeyCode(binding.scanCode);
|
||||
if (keyCode === 0 /* KeyCode.Unknown */) {
|
||||
return null;
|
||||
}
|
||||
return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode);
|
||||
}
|
||||
static resolveUserBinding(input, os) {
|
||||
const parts = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding)));
|
||||
if (parts.length > 0) {
|
||||
return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), os)];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user