Skip to content

Keybindings API

The Keybindings API provides comprehensive functionality for managing keyboard shortcuts, custom key combinations, and input handling within the development environment.

Overview

The Keybindings API enables you to:

  • Register custom keyboard shortcuts
  • Override default keybindings
  • Handle complex key combinations
  • Manage context-sensitive shortcuts
  • Provide keybinding conflict resolution
  • Support platform-specific bindings
  • Implement keybinding recording and playback
  • Handle international keyboard layouts

Basic Usage

Keybinding Registration

typescript
import { TraeAPI } from '@trae/api';

// Keybinding manager implementation
class KeybindingManager {
  private keybindings: Map<string, KeybindingEntry> = new Map();
  private contextKeys: Map<string, ContextKey> = new Map();
  private keySequences: Map<string, KeySequence> = new Map();
  private conflictResolver: ConflictResolver;
  private recordingSession: RecordingSession | null = null;

  constructor() {
    this.conflictResolver = new ConflictResolver();
    this.initializeDefaultKeybindings();
    this.setupEventListeners();
    this.loadUserKeybindings();
  }

  private initializeDefaultKeybindings(): void {
    // Register default keybindings
    const defaultBindings: KeybindingDefinition[] = [
      {
        key: 'ctrl+s',
        command: 'workbench.action.files.save',
        when: 'editorTextFocus',
        description: 'Save file'
      },
      {
        key: 'ctrl+shift+s',
        command: 'workbench.action.files.saveAs',
        when: 'editorTextFocus',
        description: 'Save file as'
      },
      {
        key: 'ctrl+o',
        command: 'workbench.action.files.openFile',
        description: 'Open file'
      },
      {
        key: 'ctrl+n',
        command: 'workbench.action.files.newUntitledFile',
        description: 'New file'
      },
      {
        key: 'ctrl+w',
        command: 'workbench.action.closeActiveEditor',
        when: 'editorIsOpen',
        description: 'Close editor'
      },
      {
        key: 'ctrl+shift+w',
        command: 'workbench.action.closeAllEditors',
        description: 'Close all editors'
      },
      {
        key: 'ctrl+z',
        command: 'undo',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Undo'
      },
      {
        key: 'ctrl+y',
        command: 'redo',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Redo'
      },
      {
        key: 'ctrl+x',
        command: 'editor.action.clipboardCutAction',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Cut'
      },
      {
        key: 'ctrl+c',
        command: 'editor.action.clipboardCopyAction',
        when: 'editorTextFocus',
        description: 'Copy'
      },
      {
        key: 'ctrl+v',
        command: 'editor.action.clipboardPasteAction',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Paste'
      },
      {
        key: 'ctrl+a',
        command: 'editor.action.selectAll',
        when: 'editorTextFocus',
        description: 'Select all'
      },
      {
        key: 'ctrl+f',
        command: 'actions.find',
        when: 'editorFocus || editorIsOpen',
        description: 'Find'
      },
      {
        key: 'ctrl+h',
        command: 'editor.action.startFindReplaceAction',
        when: 'editorFocus || editorIsOpen',
        description: 'Replace'
      },
      {
        key: 'ctrl+shift+f',
        command: 'workbench.action.findInFiles',
        description: 'Find in files'
      },
      {
        key: 'ctrl+shift+h',
        command: 'workbench.action.replaceInFiles',
        description: 'Replace in files'
      },
      {
        key: 'ctrl+g',
        command: 'workbench.action.gotoLine',
        when: 'editorFocus',
        description: 'Go to line'
      },
      {
        key: 'ctrl+shift+p',
        command: 'workbench.action.showCommands',
        description: 'Show command palette'
      },
      {
        key: 'ctrl+p',
        command: 'workbench.action.quickOpen',
        description: 'Quick open'
      },
      {
        key: 'ctrl+shift+e',
        command: 'workbench.view.explorer',
        description: 'Show explorer'
      },
      {
        key: 'ctrl+shift+g',
        command: 'workbench.view.scm',
        description: 'Show source control'
      },
      {
        key: 'ctrl+shift+d',
        command: 'workbench.view.debug',
        description: 'Show debug'
      },
      {
        key: 'ctrl+shift+x',
        command: 'workbench.view.extensions',
        description: 'Show extensions'
      },
      {
        key: 'ctrl+`',
        command: 'workbench.action.terminal.toggleTerminal',
        description: 'Toggle terminal'
      },
      {
        key: 'ctrl+shift+`',
        command: 'workbench.action.terminal.new',
        description: 'New terminal'
      },
      {
        key: 'f5',
        command: 'workbench.action.debug.start',
        when: 'debuggersAvailable && !inDebugMode',
        description: 'Start debugging'
      },
      {
        key: 'shift+f5',
        command: 'workbench.action.debug.stop',
        when: 'inDebugMode',
        description: 'Stop debugging'
      },
      {
        key: 'f9',
        command: 'editor.debug.action.toggleBreakpoint',
        when: 'debuggersAvailable && editorTextFocus',
        description: 'Toggle breakpoint'
      },
      {
        key: 'f10',
        command: 'workbench.action.debug.stepOver',
        when: 'debugState == "stopped"',
        description: 'Step over'
      },
      {
        key: 'f11',
        command: 'workbench.action.debug.stepInto',
        when: 'debugState == "stopped"',
        description: 'Step into'
      },
      {
        key: 'shift+f11',
        command: 'workbench.action.debug.stepOut',
        when: 'debugState == "stopped"',
        description: 'Step out'
      },
      {
        key: 'ctrl+k ctrl+c',
        command: 'editor.action.addCommentLine',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Add line comment'
      },
      {
        key: 'ctrl+k ctrl+u',
        command: 'editor.action.removeCommentLine',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Remove line comment'
      },
      {
        key: 'ctrl+/',
        command: 'editor.action.commentLine',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Toggle line comment'
      },
      {
        key: 'shift+alt+a',
        command: 'editor.action.blockComment',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Toggle block comment'
      },
      {
        key: 'ctrl+shift+k',
        command: 'editor.action.deleteLines',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Delete line'
      },
      {
        key: 'ctrl+enter',
        command: 'editor.action.insertLineAfter',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Insert line below'
      },
      {
        key: 'ctrl+shift+enter',
        command: 'editor.action.insertLineBefore',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Insert line above'
      },
      {
        key: 'alt+up',
        command: 'editor.action.moveLinesUpAction',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Move line up'
      },
      {
        key: 'alt+down',
        command: 'editor.action.moveLinesDownAction',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Move line down'
      },
      {
        key: 'shift+alt+up',
        command: 'editor.action.copyLinesUpAction',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Copy line up'
      },
      {
        key: 'shift+alt+down',
        command: 'editor.action.copyLinesDownAction',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Copy line down'
      },
      {
        key: 'ctrl+d',
        command: 'editor.action.addSelectionToNextFindMatch',
        when: 'editorFocus',
        description: 'Add selection to next find match'
      },
      {
        key: 'ctrl+k ctrl+d',
        command: 'editor.action.moveSelectionToNextFindMatch',
        when: 'editorFocus',
        description: 'Move last selection to next find match'
      },
      {
        key: 'ctrl+u',
        command: 'cursorUndo',
        when: 'editorTextFocus',
        description: 'Cursor undo'
      },
      {
        key: 'shift+alt+i',
        command: 'editor.action.insertCursorAtEndOfEachLineSelected',
        when: 'editorTextFocus',
        description: 'Insert cursor at end of each line selected'
      },
      {
        key: 'ctrl+shift+l',
        command: 'editor.action.selectHighlights',
        when: 'editorFocus',
        description: 'Select all occurrences of find match'
      },
      {
        key: 'ctrl+f2',
        command: 'editor.action.changeAll',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Change all occurrences'
      },
      {
        key: 'ctrl+i',
        command: 'editor.action.triggerSuggest',
        when: 'editorHasCompletionItemProvider && editorTextFocus && !editorReadonly',
        description: 'Trigger suggest'
      },
      {
        key: 'ctrl+space',
        command: 'editor.action.triggerSuggest',
        when: 'editorHasCompletionItemProvider && editorTextFocus && !editorReadonly',
        description: 'Trigger suggest'
      },
      {
        key: 'ctrl+shift+space',
        command: 'editor.action.triggerParameterHints',
        when: 'editorHasSignatureHelpProvider && editorTextFocus',
        description: 'Trigger parameter hints'
      },
      {
        key: 'shift+alt+f',
        command: 'editor.action.formatDocument',
        when: 'editorHasDocumentFormattingProvider && editorTextFocus && !editorReadonly',
        description: 'Format document'
      },
      {
        key: 'ctrl+k ctrl+f',
        command: 'editor.action.formatSelection',
        when: 'editorHasDocumentSelectionFormattingProvider && editorTextFocus && !editorReadonly',
        description: 'Format selection'
      },
      {
        key: 'f12',
        command: 'editor.action.revealDefinition',
        when: 'editorHasDefinitionProvider && editorTextFocus',
        description: 'Go to definition'
      },
      {
        key: 'alt+f12',
        command: 'editor.action.peekDefinition',
        when: 'editorHasDefinitionProvider && editorTextFocus',
        description: 'Peek definition'
      },
      {
        key: 'ctrl+k f12',
        command: 'editor.action.revealDefinitionAside',
        when: 'editorHasDefinitionProvider && editorTextFocus',
        description: 'Open definition to the side'
      },
      {
        key: 'ctrl+f12',
        command: 'editor.action.goToImplementation',
        when: 'editorHasImplementationProvider && editorTextFocus',
        description: 'Go to implementation'
      },
      {
        key: 'ctrl+shift+f12',
        command: 'editor.action.peekImplementation',
        when: 'editorHasImplementationProvider && editorTextFocus',
        description: 'Peek implementation'
      },
      {
        key: 'shift+f12',
        command: 'editor.action.goToReferences',
        when: 'editorHasReferenceProvider && editorTextFocus',
        description: 'Go to references'
      },
      {
        key: 'ctrl+k ctrl+q',
        command: 'editor.action.quickFix',
        when: 'editorHasCodeActionsProvider && editorTextFocus',
        description: 'Quick fix'
      },
      {
        key: 'f2',
        command: 'editor.action.rename',
        when: 'editorHasRenameProvider && editorTextFocus',
        description: 'Rename symbol'
      },
      {
        key: 'ctrl+k ctrl+x',
        command: 'editor.action.trimTrailingWhitespace',
        when: 'editorTextFocus && !editorReadonly',
        description: 'Trim trailing whitespace'
      },
      {
        key: 'ctrl+k m',
        command: 'workbench.action.editor.changeLanguageMode',
        when: 'editorTextFocus',
        description: 'Change language mode'
      },
      {
        key: 'ctrl+k r',
        command: 'workbench.action.files.revealActiveFileInWindows',
        when: 'editorFocus',
        description: 'Reveal active file in explorer'
      },
      {
        key: 'ctrl+k o',
        command: 'workbench.action.files.showOpenedFileInNewWindow',
        when: 'editorFocus',
        description: 'Show opened file in new window'
      },
      {
        key: 'ctrl+k v',
        command: 'workbench.action.markdown.openPreviewToTheSide',
        when: 'editorFocus && editorLangId == "markdown"',
        description: 'Open markdown preview to the side'
      }
    ];

    // Register default bindings
    defaultBindings.forEach(binding => {
      this.registerKeybinding(binding, 'default');
    });

    console.log(`Registered ${defaultBindings.length} default keybindings`);
  }

  private setupEventListeners(): void {
    // Listen for keyboard events
    document.addEventListener('keydown', this.handleKeyDown.bind(this), true);
    document.addEventListener('keyup', this.handleKeyUp.bind(this), true);
    
    // Listen for context changes
    TraeAPI.window.onDidChangeActiveTextEditor(editor => {
      this.updateContext('editorTextFocus', !!editor);
      this.updateContext('editorIsOpen', !!editor);
      this.updateContext('editorReadonly', editor?.document.isReadonly || false);
      this.updateContext('editorLangId', editor?.document.languageId || '');
    });

    // Listen for debug state changes
    TraeAPI.debug.onDidChangeActiveDebugSession(session => {
      this.updateContext('inDebugMode', !!session);
      this.updateContext('debugState', session?.state || 'inactive');
    });

    // Listen for extension changes
    TraeAPI.extensions.onDidChange(() => {
      this.updateContext('debuggersAvailable', this.hasDebuggersAvailable());
    });
  }

  private async loadUserKeybindings(): Promise<void> {
    try {
      // Load user keybindings from settings
      const userBindings = TraeAPI.workspace.getConfiguration('keybindings').get<KeybindingDefinition[]>('user', []);
      
      userBindings.forEach(binding => {
        this.registerKeybinding(binding, 'user');
      });
      
      console.log(`Loaded ${userBindings.length} user keybindings`);
    } catch (error) {
      console.error('Failed to load user keybindings:', error);
    }
  }

  // Core keybinding operations
  registerKeybinding(definition: KeybindingDefinition, source: KeybindingSource = 'extension'): string {
    try {
      const id = this.generateKeybindingId(definition);
      const entry: KeybindingEntry = {
        id,
        definition,
        source,
        enabled: true,
        registeredAt: Date.now()
      };

      // Check for conflicts
      const conflicts = this.findConflicts(definition);
      if (conflicts.length > 0) {
        const resolution = this.conflictResolver.resolve(entry, conflicts);
        if (!resolution.allow) {
          throw new Error(`Keybinding conflict: ${resolution.reason}`);
        }
        
        // Handle conflict resolution
        if (resolution.disableConflicting) {
          conflicts.forEach(conflict => {
            this.disableKeybinding(conflict.id);
          });
        }
      }

      // Parse and validate key combination
      const keyCombo = this.parseKeyCombo(definition.key);
      if (!keyCombo) {
        throw new Error(`Invalid key combination: ${definition.key}`);
      }

      entry.keyCombo = keyCombo;
      this.keybindings.set(id, entry);

      console.log(`Registered keybinding: ${definition.key} -> ${definition.command}`);
      return id;
    } catch (error) {
      console.error(`Failed to register keybinding ${definition.key}:`, error);
      throw error;
    }
  }

  unregisterKeybinding(id: string): boolean {
    try {
      const entry = this.keybindings.get(id);
      if (!entry) {
        return false;
      }

      this.keybindings.delete(id);
      console.log(`Unregistered keybinding: ${entry.definition.key}`);
      return true;
    } catch (error) {
      console.error(`Failed to unregister keybinding ${id}:`, error);
      return false;
    }
  }

  enableKeybinding(id: string): boolean {
    const entry = this.keybindings.get(id);
    if (entry) {
      entry.enabled = true;
      return true;
    }
    return false;
  }

  disableKeybinding(id: string): boolean {
    const entry = this.keybindings.get(id);
    if (entry) {
      entry.enabled = false;
      return true;
    }
    return false;
  }

  updateKeybinding(id: string, newDefinition: Partial<KeybindingDefinition>): boolean {
    try {
      const entry = this.keybindings.get(id);
      if (!entry) {
        return false;
      }

      // Create updated definition
      const updatedDefinition = { ...entry.definition, ...newDefinition };
      
      // Validate new key combination if changed
      if (newDefinition.key && newDefinition.key !== entry.definition.key) {
        const keyCombo = this.parseKeyCombo(newDefinition.key);
        if (!keyCombo) {
          throw new Error(`Invalid key combination: ${newDefinition.key}`);
        }
        entry.keyCombo = keyCombo;
      }

      entry.definition = updatedDefinition;
      console.log(`Updated keybinding: ${id}`);
      return true;
    } catch (error) {
      console.error(`Failed to update keybinding ${id}:`, error);
      return false;
    }
  }

  // Key combination parsing
  private parseKeyCombo(keyString: string): KeyCombo | null {
    try {
      const parts = keyString.toLowerCase().split(/\s+/);
      const chords: KeyChord[] = [];

      for (const part of parts) {
        const chord = this.parseKeyChord(part);
        if (!chord) {
          return null;
        }
        chords.push(chord);
      }

      return {
        chords,
        isSequence: chords.length > 1
      };
    } catch (error) {
      console.error(`Failed to parse key combination ${keyString}:`, error);
      return null;
    }
  }

  private parseKeyChord(chordString: string): KeyChord | null {
    const parts = chordString.split('+');
    const modifiers: KeyModifier[] = [];
    let key = '';

    for (const part of parts) {
      const normalizedPart = this.normalizeKeyName(part);
      
      if (this.isModifier(normalizedPart)) {
        modifiers.push(normalizedPart as KeyModifier);
      } else {
        if (key) {
          // Multiple non-modifier keys not allowed
          return null;
        }
        key = normalizedPart;
      }
    }

    if (!key) {
      return null;
    }

    return {
      key,
      modifiers: modifiers.sort(), // Sort for consistent comparison
      ctrlKey: modifiers.includes('ctrl'),
      altKey: modifiers.includes('alt'),
      shiftKey: modifiers.includes('shift'),
      metaKey: modifiers.includes('meta')
    };
  }

  private normalizeKeyName(key: string): string {
    const keyMap: { [key: string]: string } = {
      'control': 'ctrl',
      'command': 'meta',
      'cmd': 'meta',
      'option': 'alt',
      'escape': 'esc',
      'return': 'enter',
      'space': ' ',
      'plus': '+',
      'minus': '-',
      'equal': '=',
      'backquote': '`',
      'backslash': '\\',
      'bracketleft': '[',
      'bracketright': ']',
      'semicolon': ';',
      'quote': "'",
      'comma': ',',
      'period': '.',
      'slash': '/'
    };

    return keyMap[key.toLowerCase()] || key.toLowerCase();
  }

  private isModifier(key: string): boolean {
    return ['ctrl', 'alt', 'shift', 'meta'].includes(key);
  }

  // Event handling
  private handleKeyDown(event: KeyboardEvent): void {
    try {
      // Skip if recording
      if (this.recordingSession) {
        this.recordingSession.recordKey(event);
        return;
      }

      // Create current key chord
      const currentChord = this.createKeyChordFromEvent(event);
      if (!currentChord) {
        return;
      }

      // Find matching keybindings
      const matches = this.findMatchingKeybindings(currentChord);
      
      if (matches.length === 0) {
        return;
      }

      // Handle single chord matches
      const singleChordMatches = matches.filter(match => !match.keyCombo.isSequence);
      if (singleChordMatches.length > 0) {
        const bestMatch = this.selectBestMatch(singleChordMatches);
        if (bestMatch && this.evaluateWhenCondition(bestMatch.definition.when)) {
          event.preventDefault();
          event.stopPropagation();
          this.executeKeybinding(bestMatch);
          return;
        }
      }

      // Handle sequence matches
      const sequenceMatches = matches.filter(match => match.keyCombo.isSequence);
      if (sequenceMatches.length > 0) {
        this.handleKeySequence(currentChord, sequenceMatches, event);
      }
    } catch (error) {
      console.error('Error handling key down:', error);
    }
  }

  private handleKeyUp(event: KeyboardEvent): void {
    // Handle key up events if needed
  }

  private createKeyChordFromEvent(event: KeyboardEvent): KeyChord | null {
    const key = this.normalizeKeyName(event.key);
    
    // Skip modifier-only keys
    if (this.isModifier(key)) {
      return null;
    }

    const modifiers: KeyModifier[] = [];
    if (event.ctrlKey) modifiers.push('ctrl');
    if (event.altKey) modifiers.push('alt');
    if (event.shiftKey && !this.isShiftableKey(key)) modifiers.push('shift');
    if (event.metaKey) modifiers.push('meta');

    return {
      key,
      modifiers: modifiers.sort(),
      ctrlKey: event.ctrlKey,
      altKey: event.altKey,
      shiftKey: event.shiftKey,
      metaKey: event.metaKey
    };
  }

  private isShiftableKey(key: string): boolean {
    // Keys that naturally use shift (uppercase letters, symbols)
    return /^[A-Z!@#$%^&*()_+{}|:"<>?]$/.test(key);
  }

  private findMatchingKeybindings(chord: KeyChord): KeybindingEntry[] {
    const matches: KeybindingEntry[] = [];

    for (const entry of this.keybindings.values()) {
      if (!entry.enabled || !entry.keyCombo) {
        continue;
      }

      // Check if first chord matches
      const firstChord = entry.keyCombo.chords[0];
      if (this.chordsMatch(chord, firstChord)) {
        matches.push(entry);
      }
    }

    return matches;
  }

  private chordsMatch(chord1: KeyChord, chord2: KeyChord): boolean {
    return (
      chord1.key === chord2.key &&
      chord1.ctrlKey === chord2.ctrlKey &&
      chord1.altKey === chord2.altKey &&
      chord1.shiftKey === chord2.shiftKey &&
      chord1.metaKey === chord2.metaKey
    );
  }

  private selectBestMatch(matches: KeybindingEntry[]): KeybindingEntry | null {
    if (matches.length === 0) {
      return null;
    }

    // Sort by priority: user > extension > default
    const priorityOrder = { user: 3, extension: 2, default: 1 };
    
    matches.sort((a, b) => {
      const aPriority = priorityOrder[a.source] || 0;
      const bPriority = priorityOrder[b.source] || 0;
      
      if (aPriority !== bPriority) {
        return bPriority - aPriority;
      }
      
      // If same priority, prefer more recent
      return b.registeredAt - a.registeredAt;
    });

    return matches[0];
  }

  private handleKeySequence(chord: KeyChord, matches: KeybindingEntry[], event: KeyboardEvent): void {
    // Implementation for handling key sequences
    // This would involve tracking partial matches and waiting for subsequent keys
    console.log('Handling key sequence:', chord, matches);
  }

  // Context evaluation
  private evaluateWhenCondition(when?: string): boolean {
    if (!when) {
      return true;
    }

    try {
      return this.parseAndEvaluateCondition(when);
    } catch (error) {
      console.error(`Error evaluating when condition "${when}":`, error);
      return false;
    }
  }

  private parseAndEvaluateCondition(condition: string): boolean {
    // Simple condition parser
    // Supports: &&, ||, !, parentheses, context keys
    
    // Remove whitespace
    condition = condition.replace(/\s+/g, '');
    
    // Handle parentheses
    while (condition.includes('(')) {
      const start = condition.lastIndexOf('(');
      const end = condition.indexOf(')', start);
      
      if (end === -1) {
        throw new Error('Unmatched parentheses');
      }
      
      const subCondition = condition.substring(start + 1, end);
      const result = this.parseAndEvaluateCondition(subCondition);
      condition = condition.substring(0, start) + result.toString() + condition.substring(end + 1);
    }
    
    // Handle OR operators
    if (condition.includes('||')) {
      const parts = condition.split('||');
      return parts.some(part => this.parseAndEvaluateCondition(part));
    }
    
    // Handle AND operators
    if (condition.includes('&&')) {
      const parts = condition.split('&&');
      return parts.every(part => this.parseAndEvaluateCondition(part));
    }
    
    // Handle NOT operator
    if (condition.startsWith('!')) {
      return !this.parseAndEvaluateCondition(condition.substring(1));
    }
    
    // Handle boolean literals
    if (condition === 'true') return true;
    if (condition === 'false') return false;
    
    // Handle context key
    return this.getContextValue(condition);
  }

  private getContextValue(key: string): boolean {
    const contextKey = this.contextKeys.get(key);
    if (contextKey) {
      return contextKey.get();
    }
    
    // Default context values
    const defaultValues: { [key: string]: boolean } = {
      'editorTextFocus': false,
      'editorIsOpen': false,
      'editorReadonly': false,
      'inDebugMode': false,
      'debuggersAvailable': false
    };
    
    return defaultValues[key] || false;
  }

  updateContext(key: string, value: boolean): void {
    let contextKey = this.contextKeys.get(key);
    if (!contextKey) {
      contextKey = new ContextKey(key, value);
      this.contextKeys.set(key, contextKey);
    } else {
      contextKey.set(value);
    }
  }

  // Command execution
  private async executeKeybinding(entry: KeybindingEntry): Promise<void> {
    try {
      console.log(`Executing keybinding: ${entry.definition.key} -> ${entry.definition.command}`);
      
      // Execute the command
      await TraeAPI.commands.executeCommand(entry.definition.command, entry.definition.args);
      
      // Update usage statistics
      entry.lastUsed = Date.now();
      entry.usageCount = (entry.usageCount || 0) + 1;
    } catch (error) {
      console.error(`Failed to execute keybinding command ${entry.definition.command}:`, error);
      TraeAPI.window.showErrorMessage(`Failed to execute command: ${entry.definition.command}`);
    }
  }

  // Conflict resolution
  private findConflicts(definition: KeybindingDefinition): KeybindingEntry[] {
    const conflicts: KeybindingEntry[] = [];
    
    for (const entry of this.keybindings.values()) {
      if (!entry.enabled) {
        continue;
      }
      
      if (entry.definition.key === definition.key) {
        // Check if contexts overlap
        if (this.contextsOverlap(entry.definition.when, definition.when)) {
          conflicts.push(entry);
        }
      }
    }
    
    return conflicts;
  }

  private contextsOverlap(when1?: string, when2?: string): boolean {
    // Simplified context overlap detection
    // In a real implementation, this would be more sophisticated
    if (!when1 && !when2) {
      return true; // Both global
    }
    
    if (!when1 || !when2) {
      return true; // One is global, potential overlap
    }
    
    return when1 === when2; // Same context
  }

  private hasDebuggersAvailable(): boolean {
    return TraeAPI.extensions.all.some(ext => 
      ext.packageJSON.contributes?.debuggers?.length > 0
    );
  }

  // Keybinding recording
  startRecording(): RecordingSession {
    if (this.recordingSession) {
      this.stopRecording();
    }
    
    this.recordingSession = new RecordingSession();
    console.log('Started keybinding recording');
    return this.recordingSession;
  }

  stopRecording(): KeySequence | null {
    if (!this.recordingSession) {
      return null;
    }
    
    const sequence = this.recordingSession.getSequence();
    this.recordingSession = null;
    
    console.log('Stopped keybinding recording');
    return sequence;
  }

  isRecording(): boolean {
    return !!this.recordingSession;
  }

  // Utility methods
  getAllKeybindings(): KeybindingEntry[] {
    return Array.from(this.keybindings.values());
  }

  getKeybindingsByCommand(command: string): KeybindingEntry[] {
    return this.getAllKeybindings().filter(entry => 
      entry.definition.command === command
    );
  }

  getKeybindingsBySource(source: KeybindingSource): KeybindingEntry[] {
    return this.getAllKeybindings().filter(entry => 
      entry.source === source
    );
  }

  searchKeybindings(query: string): KeybindingEntry[] {
    const lowercaseQuery = query.toLowerCase();
    
    return this.getAllKeybindings().filter(entry => {
      const definition = entry.definition;
      return (
        definition.key.toLowerCase().includes(lowercaseQuery) ||
        definition.command.toLowerCase().includes(lowercaseQuery) ||
        (definition.description && definition.description.toLowerCase().includes(lowercaseQuery))
      );
    });
  }

  exportKeybindings(): KeybindingExport {
    const userBindings = this.getKeybindingsBySource('user');
    
    return {
      version: '1.0.0',
      exportedAt: new Date().toISOString(),
      keybindings: userBindings.map(entry => entry.definition)
    };
  }

  async importKeybindings(exportData: KeybindingExport): Promise<boolean> {
    try {
      // Clear existing user keybindings
      const userBindings = this.getKeybindingsBySource('user');
      userBindings.forEach(entry => {
        this.unregisterKeybinding(entry.id);
      });
      
      // Import new keybindings
      exportData.keybindings.forEach(definition => {
        this.registerKeybinding(definition, 'user');
      });
      
      console.log(`Imported ${exportData.keybindings.length} keybindings`);
      return true;
    } catch (error) {
      console.error('Failed to import keybindings:', error);
      return false;
    }
  }

  private generateKeybindingId(definition: KeybindingDefinition): string {
    return `${definition.key}:${definition.command}:${Date.now()}`;
  }

  dispose(): void {
    document.removeEventListener('keydown', this.handleKeyDown.bind(this), true);
    document.removeEventListener('keyup', this.handleKeyUp.bind(this), true);
    
    this.keybindings.clear();
    this.contextKeys.clear();
    this.keySequences.clear();
    
    if (this.recordingSession) {
      this.recordingSession.dispose();
    }
  }
}

// Supporting classes
class ContextKey {
  private value: boolean;
  private listeners: ((value: boolean) => void)[] = [];

  constructor(private key: string, initialValue: boolean = false) {
    this.value = initialValue;
  }

  get(): boolean {
    return this.value;
  }

  set(value: boolean): void {
    if (this.value !== value) {
      this.value = value;
      this.notifyListeners();
    }
  }

  onDidChange(listener: (value: boolean) => void): TraeAPI.Disposable {
    this.listeners.push(listener);
    
    return {
      dispose: () => {
        const index = this.listeners.indexOf(listener);
        if (index >= 0) {
          this.listeners.splice(index, 1);
        }
      }
    };
  }

  private notifyListeners(): void {
    this.listeners.forEach(listener => {
      try {
        listener(this.value);
      } catch (error) {
        console.error(`Error in context key listener for ${this.key}:`, error);
      }
    });
  }
}

class ConflictResolver {
  resolve(newBinding: KeybindingEntry, conflicts: KeybindingEntry[]): ConflictResolution {
    // Simple conflict resolution strategy
    // In a real implementation, this could be more sophisticated
    
    const highPriorityConflicts = conflicts.filter(conflict => 
      conflict.source === 'user' || conflict.source === 'extension'
    );
    
    if (highPriorityConflicts.length > 0) {
      return {
        allow: false,
        reason: 'Conflicts with existing high-priority keybinding',
        disableConflicting: false
      };
    }
    
    return {
      allow: true,
      reason: 'No high-priority conflicts',
      disableConflicting: true
    };
  }
}

class RecordingSession {
  private recordedKeys: KeyboardEvent[] = [];
  private startTime: number;

  constructor() {
    this.startTime = Date.now();
  }

  recordKey(event: KeyboardEvent): void {
    this.recordedKeys.push({
      ...event,
      timeStamp: event.timeStamp - this.startTime
    } as KeyboardEvent);
  }

  getSequence(): KeySequence {
    return {
      keys: this.recordedKeys.map(event => ({
        key: event.key,
        ctrlKey: event.ctrlKey,
        altKey: event.altKey,
        shiftKey: event.shiftKey,
        metaKey: event.metaKey,
        timestamp: event.timeStamp
      })),
      duration: Date.now() - this.startTime
    };
  }

  dispose(): void {
    this.recordedKeys = [];
  }
}

// Interfaces
interface KeybindingDefinition {
  key: string;
  command: string;
  when?: string;
  args?: any;
  description?: string;
}

interface KeybindingEntry {
  id: string;
  definition: KeybindingDefinition;
  source: KeybindingSource;
  enabled: boolean;
  registeredAt: number;
  lastUsed?: number;
  usageCount?: number;
  keyCombo?: KeyCombo;
}

type KeybindingSource = 'default' | 'extension' | 'user';

interface KeyCombo {
  chords: KeyChord[];
  isSequence: boolean;
}

interface KeyChord {
  key: string;
  modifiers: KeyModifier[];
  ctrlKey: boolean;
  altKey: boolean;
  shiftKey: boolean;
  metaKey: boolean;
}

type KeyModifier = 'ctrl' | 'alt' | 'shift' | 'meta';

interface KeySequence {
  keys: {
    key: string;
    ctrlKey: boolean;
    altKey: boolean;
    shiftKey: boolean;
    metaKey: boolean;
    timestamp: number;
  }[];
  duration: number;
}

interface ConflictResolution {
  allow: boolean;
  reason: string;
  disableConflicting: boolean;
}

interface KeybindingExport {
  version: string;
  exportedAt: string;
  keybindings: KeybindingDefinition[];
}

// Initialize keybinding manager
const keybindingManager = new KeybindingManager();

Platform-Specific Keybindings

typescript
// Platform detection and key mapping
class PlatformKeybindings {
  private platform: 'windows' | 'macos' | 'linux';
  private keyMappings: Map<string, PlatformKeyMapping>;

  constructor() {
    this.platform = this.detectPlatform();
    this.keyMappings = new Map();
    this.initializePlatformMappings();
  }

  private detectPlatform(): 'windows' | 'macos' | 'linux' {
    const userAgent = navigator.userAgent.toLowerCase();
    
    if (userAgent.includes('mac')) {
      return 'macos';
    } else if (userAgent.includes('win')) {
      return 'windows';
    } else {
      return 'linux';
    }
  }

  private initializePlatformMappings(): void {
    // Common cross-platform mappings
    const commonMappings: PlatformKeyMapping[] = [
      {
        command: 'workbench.action.files.save',
        windows: 'ctrl+s',
        macos: 'cmd+s',
        linux: 'ctrl+s'
      },
      {
        command: 'workbench.action.files.openFile',
        windows: 'ctrl+o',
        macos: 'cmd+o',
        linux: 'ctrl+o'
      },
      {
        command: 'workbench.action.files.newUntitledFile',
        windows: 'ctrl+n',
        macos: 'cmd+n',
        linux: 'ctrl+n'
      },
      {
        command: 'workbench.action.closeActiveEditor',
        windows: 'ctrl+w',
        macos: 'cmd+w',
        linux: 'ctrl+w'
      },
      {
        command: 'undo',
        windows: 'ctrl+z',
        macos: 'cmd+z',
        linux: 'ctrl+z'
      },
      {
        command: 'redo',
        windows: 'ctrl+y',
        macos: 'cmd+shift+z',
        linux: 'ctrl+y'
      },
      {
        command: 'editor.action.clipboardCutAction',
        windows: 'ctrl+x',
        macos: 'cmd+x',
        linux: 'ctrl+x'
      },
      {
        command: 'editor.action.clipboardCopyAction',
        windows: 'ctrl+c',
        macos: 'cmd+c',
        linux: 'ctrl+c'
      },
      {
        command: 'editor.action.clipboardPasteAction',
        windows: 'ctrl+v',
        macos: 'cmd+v',
        linux: 'ctrl+v'
      },
      {
        command: 'editor.action.selectAll',
        windows: 'ctrl+a',
        macos: 'cmd+a',
        linux: 'ctrl+a'
      },
      {
        command: 'actions.find',
        windows: 'ctrl+f',
        macos: 'cmd+f',
        linux: 'ctrl+f'
      },
      {
        command: 'workbench.action.showCommands',
        windows: 'ctrl+shift+p',
        macos: 'cmd+shift+p',
        linux: 'ctrl+shift+p'
      },
      {
        command: 'workbench.action.quickOpen',
        windows: 'ctrl+p',
        macos: 'cmd+p',
        linux: 'ctrl+p'
      }
    ];

    commonMappings.forEach(mapping => {
      this.keyMappings.set(mapping.command, mapping);
    });
  }

  getKeybindingForPlatform(command: string): string | undefined {
    const mapping = this.keyMappings.get(command);
    if (!mapping) {
      return undefined;
    }

    switch (this.platform) {
      case 'windows':
        return mapping.windows;
      case 'macos':
        return mapping.macos;
      case 'linux':
        return mapping.linux;
      default:
        return mapping.windows; // fallback
    }
  }

  getCurrentPlatform(): string {
    return this.platform;
  }

  addPlatformMapping(mapping: PlatformKeyMapping): void {
    this.keyMappings.set(mapping.command, mapping);
  }

  getAllMappings(): PlatformKeyMapping[] {
    return Array.from(this.keyMappings.values());
  }
}

interface PlatformKeyMapping {
  command: string;
  windows: string;
  macos: string;
  linux: string;
}

// Initialize platform keybindings
const platformKeybindings = new PlatformKeybindings();

API Reference

Core Interfaces

typescript
interface KeybindingsAPI {
  // Registration
  registerKeybinding(definition: KeybindingDefinition, source?: KeybindingSource): string;
  unregisterKeybinding(id: string): boolean;
  updateKeybinding(id: string, newDefinition: Partial<KeybindingDefinition>): boolean;
  
  // Management
  enableKeybinding(id: string): boolean;
  disableKeybinding(id: string): boolean;
  
  // Query
  getAllKeybindings(): KeybindingEntry[];
  getKeybindingsByCommand(command: string): KeybindingEntry[];
  getKeybindingsBySource(source: KeybindingSource): KeybindingEntry[];
  searchKeybindings(query: string): KeybindingEntry[];
  
  // Context
  updateContext(key: string, value: boolean): void;
  
  // Recording
  startRecording(): RecordingSession;
  stopRecording(): KeySequence | null;
  isRecording(): boolean;
  
  // Import/Export
  exportKeybindings(): KeybindingExport;
  importKeybindings(exportData: KeybindingExport): Promise<boolean>;
  
  // Platform
  getKeybindingForPlatform(command: string): string | undefined;
  getCurrentPlatform(): string;
}

Best Practices

  1. Key Combinations: Use standard modifier keys and avoid conflicts
  2. Context Conditions: Use specific when conditions to avoid conflicts
  3. Platform Compatibility: Provide platform-specific bindings when needed
  4. User Customization: Allow users to override default keybindings
  5. Documentation: Document all custom keybindings clearly
  6. Accessibility: Ensure keybindings are accessible and discoverable
  7. Performance: Optimize key event handling for responsiveness
  8. Conflict Resolution: Implement clear conflict resolution strategies

Your Ultimate AI-Powered IDE Learning Guide