26 Command Definition in package.json

26.1 From Eclipse Manifests to Declarative VSCode Architecture

Eclipse plugin developers are familiar with distributing configuration across plugin.xml, MANIFEST.MF, and feature.xml. VSCode consolidates these functions into package.json as a central configuration file. The crucial difference lies in the timing: while Eclipse discovers extension points at runtime through XML parsing, VSCode reads the package.json at startup, enabling it to display commands in the command palette immediately without loading the extension.

This declarative architecture acts as a contract between the extension and VSCode. The package.json promises functionality that must later be fulfilled by TypeScript code.

26.2 Basic Structure of the Extension Manifest

The VSCode manifest follows a clear schema that separates metadata from functionality declarations:

{
  "name": "entwicklungstools",
  "displayName": "Professionelle Entwicklungstools",
  "version": "1.0.0",
  
  "engines": {
    "vscode": "^1.80.0"  // Minimum version for compatibility
  },
  
  "activationEvents": [
    "onCommand:tools.analyseCode"  // When the extension is loaded
  ],
  
  "main": "./out/extension.js",  // Entry point of the extension
  
  "contributes": {
    // All UI contributions are declared here
    "commands": [],
    "menus": {},
    "keybindings": []
  }
}

The contributes section serves as a public API declaration. VSCode interprets this metadata without executing any code and uses it to populate menus and the command palette efficiently.

26.3 Command Definition in contributes.commands

Each command requires a unique identifier and metadata for the user interface:

{
  "contributes": {
    "commands": [
      {
        "command": "tools.analyseCode",       // Unique ID (like fully qualified class name)
        "title": "Analyze Code Quality",      // Display name in command palette
        "category": "Development Tools",      // Grouping for better discoverability
        "icon": "$(search)"                   // VSCode codicon for toolbar display
      }
    ]
  }
}

The command ID follows the convention namespace.action and is used both for UI declaration and code registration. VSCode treats this ID as an immutable key between the frontend and backend of the extension.

26.4 Interaction Between Declaration and Implementation

The package.json promises functionality that must be implemented in TypeScript code. The command ID acts as a contract:

// extension.ts – Implementation of the promised command
export function activate(context: vscode.ExtensionContext) {
    const analyseCommand = vscode.commands.registerCommand(
        'tools.analyseCode', // Must match package.json exactly
        () => {
            vscode.window.showInformationMessage('Analyzing code...');
        }
    );
    
    // Important: avoid memory leaks through subscription management
    context.subscriptions.push(analyseCommand);
}

This separation allows VSCode to display commands before the extension is loaded. The TypeScript code is only activated and executed upon the first invocation.

While commands defines available actions, menus determines where these actions appear in the UI. The when attribute controls contextual visibility:

{
  "contributes": {
    "menus": {
      "commandPalette": [
        {
          "command": "tools.analyseCode",
          "when": "editorIsOpen"  // Visible only when editor is open
        }
      ],
      
      "editor/context": [
        {
          "command": "tools.analyseCode",
          "when": "editorLangId == typescript && editorHasSelection",
          "group": "navigation@1"  // Positioning in context menu
        }
      ]
    }
  }
}

The most important context variables for when-clauses are editorLangId (language of the current file), editorHasSelection (text is selected), editorIsOpen (editor is active), and explorerResourceIsFolder (a folder is selected in the explorer). These conditions can be combined using logical operators such as &&, ||, and !.

26.6 Keybindings for Direct Access

Keybindings provide direct access to frequently used functions and respect platform-specific conventions:

{
  "contributes": {
    "keybindings": [
      {
        "command": "tools.analyseCode",
        "key": "ctrl+shift+a",        // Windows/Linux
        "mac": "cmd+shift+a",         // macOS-specific key
        "when": "editorTextFocus && editorLangId == typescript"
      }
    ]
  }
}

VSCode also supports multi-step keybindings (chord keys) with the syntax ctrl+k ctrl+m, where keys are pressed sequentially. Use keybindings sparingly and only for frequently used functions to avoid conflicts with other extensions.

26.7 Best Practices and Common Pitfalls

Systematic command development follows proven patterns and avoids typical pitfalls:

Command ID Consistency: The most common source of errors is typos between package.json and registerCommand. Use copy-paste to transfer IDs and define constants for reused command names.

Subscription Management: Every registered command must be added to context.subscriptions. Forgotten registrations lead to memory leaks, as VSCode does not automatically remove commands when the extension is deactivated.

When-Clause Specificity: Conditions that are too broad dilute the user experience. Specific context conditions like editorLangId == typescript improve performance and the relevance of displayed options.

Keybinding Conflicts: Check new shortcuts against VSCode’s default keybindings and popular extensions. The Context Inspector (available via command palette) helps debug complex when-clauses.