Skip to main content

Debugging API

The Debugging API allows extensions to integrate debugging capabilities into VS Code. You can create debug adapters, provide debug configurations, and control debug sessions programmatically.

Namespace

vscode.debug
All debugging functionality is available through the debug namespace.

Debug Sessions

Accessing Active Session

import * as vscode from 'vscode';

// Get active debug session
const session = vscode.debug.activeDebugSession;
if (session) {
    console.log('Active session:', session.name);
    console.log('Type:', session.type);
    console.log('ID:', session.id);
}

// Access debug console
const console = vscode.debug.activeDebugConsole;
console.appendLine('Debug message');
console.append('Inline message');
id
string
Unique identifier of the debug session
type
string
Debug session type from the configuration
name
string
Human-readable session name (can be modified)
workspaceFolder
WorkspaceFolder | undefined
Workspace folder for this session
configuration
DebugConfiguration
Resolved debug configuration with substituted variables

Debug Session Events

vscode.debug.onDidStartDebugSession(session => {
    console.log('Debug session started:', session.name);
});

vscode.debug.onDidTerminateDebugSession(session => {
    console.log('Debug session ended:', session.name);
});

vscode.debug.onDidChangeActiveDebugSession(session => {
    if (session) {
        console.log('Active session changed to:', session.name);
    } else {
        console.log('No active debug session');
    }
});

Starting Debug Sessions

Start Debugging

// Start with named configuration
const folder = vscode.workspace.workspaceFolders?.[0];
await vscode.debug.startDebugging(
    folder,
    'Launch Program'  // Name from launch.json
);

// Start with debug configuration object
const config: vscode.DebugConfiguration = {
    type: 'node',
    name: 'Launch',
    request: 'launch',
    program: '${workspaceFolder}/app.js',
    stopOnEntry: true
};

await vscode.debug.startDebugging(folder, config);

// Start with options
await vscode.debug.startDebugging(folder, config, {
    noDebug: false,
    compact: false,
    suppressDebugToolbar: false,
    suppressDebugStatusbar: false,
    suppressDebugView: false
});
folder
WorkspaceFolder | undefined
Workspace folder for resolving variables
nameOrConfiguration
string | DebugConfiguration
required
Configuration name or object
options
DebugSessionOptions
Additional session options

Stop Debugging

// Stop specific session
const session = vscode.debug.activeDebugSession;
if (session) {
    await vscode.debug.stopDebugging(session);
}

// Stop all sessions
await vscode.debug.stopDebugging();

Breakpoints

Managing Breakpoints

// Get all breakpoints
const breakpoints = vscode.debug.breakpoints;
console.log(`Total breakpoints: ${breakpoints.length}`);

// Add breakpoints
const bp1 = new vscode.SourceBreakpoint(
    new vscode.Location(
        vscode.Uri.file('/path/to/file.ts'),
        new vscode.Position(10, 0)
    ),
    true,  // enabled
    'x > 10',  // condition
    '5'  // hit condition
);

const bp2 = new vscode.FunctionBreakpoint(
    'myFunction',
    true,
    'count > 100'
);

vscode.debug.addBreakpoints([bp1, bp2]);

// Remove breakpoints
vscode.debug.removeBreakpoints([bp1, bp2]);

Breakpoint Events

vscode.debug.onDidChangeBreakpoints(event => {
    console.log('Added:', event.added);
    console.log('Removed:', event.removed);
    console.log('Changed:', event.changed);
    
    for (const bp of event.added) {
        if (bp instanceof vscode.SourceBreakpoint) {
            console.log('Source breakpoint:', bp.location);
        } else if (bp instanceof vscode.FunctionBreakpoint) {
            console.log('Function breakpoint:', bp.functionName);
        }
    }
});

Debug Configuration Provider

Providing Debug Configurations

vscode.debug.registerDebugConfigurationProvider(
    'node',
    {
        provideDebugConfigurations(folder, token) {
            // Provide initial configurations
            return [
                {
                    type: 'node',
                    request: 'launch',
                    name: 'Launch Program',
                    program: '${workspaceFolder}/app.js'
                }
            ];
        },
        
        resolveDebugConfiguration(folder, config, token) {
            // Resolve and validate configuration
            if (!config.type && !config.request && !config.name) {
                // Return null to abort launch
                return null;
            }
            
            // Add missing properties
            if (!config.program) {
                config.program = '${workspaceFolder}/app.js';
            }
            
            return config;
        },
        
        resolveDebugConfigurationWithSubstitutedVariables(folder, config, token) {
            // Final validation after variable substitution
            if (!validateConfig(config)) {
                vscode.window.showErrorMessage('Invalid debug configuration');
                return undefined;
            }
            
            return config;
        }
    },
    vscode.DebugConfigurationProviderTriggerKind.Dynamic
);
Use DebugConfigurationProviderTriggerKind.Initial for initial launch.json configurations, and Dynamic for configurations shown in the debug dropdown.

Debug Adapter

Debug Adapter Descriptor Factory

vscode.debug.registerDebugAdapterDescriptorFactory('myDebugType', {
    createDebugAdapterDescriptor(session, executable) {
        // Return executable adapter
        return new vscode.DebugAdapterExecutable(
            '/path/to/adapter',
            ['--arg1', 'value1'],
            {
                env: { DEBUG: 'true' },
                cwd: '/working/dir'
            }
        );
        
        // Or return server adapter
        return new vscode.DebugAdapterServer(4711, 'localhost');
        
        // Or return named pipe adapter
        return new vscode.DebugAdapterNamedPipeServer('/tmp/debug-pipe');
        
        // Or return inline implementation
        return new vscode.DebugAdapterInlineImplementation(customAdapter);
    }
});
new vscode.DebugAdapterExecutable(
    'node',
    ['/path/to/debugAdapter.js'],
    { cwd: workspaceFolder }
)

Debug Adapter Tracker

vscode.debug.registerDebugAdapterTrackerFactory('*', {
    createDebugAdapterTracker(session) {
        return {
            onWillStartSession() {
                console.log('Session starting:', session.name);
            },
            onWillReceiveMessage(message) {
                console.log('-> Adapter:', message);
            },
            onDidSendMessage(message) {
                console.log('<- Adapter:', message);
            },
            onWillStopSession() {
                console.log('Session stopping');
            },
            onError(error) {
                console.error('Adapter error:', error);
            },
            onExit(code, signal) {
                console.log('Adapter exited:', code, signal);
            }
        };
    }
});

Custom Requests

Sending Custom Requests

const session = vscode.debug.activeDebugSession;
if (session) {
    // Send custom DAP request
    const response = await session.customRequest('evaluate', {
        expression: 'myVariable',
        context: 'watch'
    });
    
    console.log('Evaluation result:', response.result);
    
    // Get stack trace
    const stackTrace = await session.customRequest('stackTrace', {
        threadId: 1
    });
}

Debug Console

Writing to Debug Console

const debugConsole = vscode.debug.activeDebugConsole;

// Append text
debugConsole.append('Processing...');

// Append line
debugConsole.appendLine('Debug: Value = 42');
debugConsole.appendLine(`Variable: ${JSON.stringify(obj, null, 2)}`);

Package.json Contributions

Define debug configurations in your extension manifest:
package.json
{
  "contributes": {
    "debuggers": [
      {
        "type": "node",
        "label": "Node Debug",
        "program": "./out/debugAdapter.js",
        "runtime": "node",
        "configurationAttributes": {
          "launch": {
            "required": ["program"],
            "properties": {
              "program": {
                "type": "string",
                "description": "The program to debug"
              },
              "stopOnEntry": {
                "type": "boolean",
                "description": "Automatically stop after launch",
                "default": false
              }
            }
          },
          "attach": {
            "required": ["port"],
            "properties": {
              "port": {
                "type": "number",
                "description": "Port to attach to"
              }
            }
          }
        },
        "initialConfigurations": [
          {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "program": "${workspaceFolder}/app.js"
          }
        ],
        "configurationSnippets": [
          {
            "label": "Node.js: Launch Program",
            "description": "Launch a node program in debug mode",
            "body": {
              "type": "node",
              "request": "launch",
              "name": "Launch Program",
              "program": "${workspaceFolder}/^\"app.js\""
            }
          }
        ]
      }
    ],
    "breakpoints": [
      {
        "language": "javascript"
      },
      {
        "language": "typescript"
      }
    ]
  }
}

Debug Configuration

DebugConfiguration Interface

interface DebugConfiguration {
    type: string;      // Debugger type
    name: string;      // Configuration name
    request: string;   // 'launch' or 'attach'
    [key: string]: any; // Type-specific properties
}

Creating Configurations

const config: vscode.DebugConfiguration = {
    type: 'node',
    name: 'Debug Tests',
    request: 'launch',
    program: '${workspaceFolder}/test.js',
    args: ['--verbose'],
    cwd: '${workspaceFolder}',
    env: {
        NODE_ENV: 'test'
    },
    stopOnEntry: false,
    console: 'integratedTerminal'
};

// Start debugging with this config
await vscode.debug.startDebugging(undefined, config);

Debug Adapter Protocol

Implementing Debug Adapter

class MyDebugAdapter implements vscode.DebugAdapter {
    private messageEmitter = new vscode.EventEmitter<vscode.DebugProtocolMessage>();
    
    readonly onDidSendMessage = this.messageEmitter.event;
    
    handleMessage(message: vscode.DebugProtocolMessage): void {
        // Process DAP message from VS Code
        console.log('Received message:', message);
        
        // Send response
        this.messageEmitter.fire({
            seq: 1,
            type: 'response',
            request_seq: message.seq,
            command: message.command,
            success: true,
            body: {}
        });
    }
    
    dispose(): void {
        this.messageEmitter.dispose();
    }
}

// Register inline debug adapter
vscode.debug.registerDebugAdapterDescriptorFactory('myDebugType', {
    createDebugAdapterDescriptor(session) {
        return new vscode.DebugAdapterInlineImplementation(
            new MyDebugAdapter()
        );
    }
});

Evaluatable Expressions

Evaluatable Expression Provider

vscode.languages.registerEvaluatableExpressionProvider('javascript', {
    provideEvaluatableExpression(document, position, token) {
        const range = document.getWordRangeAtPosition(position);
        const word = document.getText(range);
        
        // Return expression that debugger should evaluate
        if (isValidExpression(word)) {
            return new vscode.EvaluatableExpression(range, word);
        }
        
        return undefined;
    }
});

Inline Values Provider

vscode.languages.registerInlineValuesProvider('python', {
    provideInlineValues(document, viewPort, context, token) {
        const values: vscode.InlineValue[] = [];
        
        // Show variable values inline during debugging
        if (context.stoppedLocation) {
            values.push(
                new vscode.InlineValueVariableLookup(
                    new vscode.Range(5, 0, 5, 10),
                    'myVariable'
                )
            );
            
            values.push(
                new vscode.InlineValueEvaluatableExpression(
                    new vscode.Range(6, 0, 6, 15),
                    'obj.property'
                )
            );
            
            values.push(
                new vscode.InlineValueText(
                    new vscode.Range(7, 0, 7, 5),
                    'Custom text'
                )
            );
        }
        
        return values;
    }
});

Advanced Features

Parent-Child Debug Sessions

// Start child debug session
const parentSession = vscode.debug.activeDebugSession;

if (parentSession) {
    const childConfig: vscode.DebugConfiguration = {
        type: 'node',
        name: 'Child Process',
        request: 'attach',
        port: 9229
    };
    
    await vscode.debug.startDebugging(
        undefined,
        childConfig,
        {
            parentSession: parentSession,
            consoleMode: vscode.DebugConsoleMode.MergeWithParent
        }
    );
}

Debug Protocol Breakpoints

const session = vscode.debug.activeDebugSession;
if (session) {
    const breakpoint = vscode.debug.breakpoints[0];
    
    // Get DAP breakpoint
    const dapBreakpoint = await session.getDebugProtocolBreakpoint(
        breakpoint
    );
    
    if (dapBreakpoint) {
        console.log('DAP breakpoint:', dapBreakpoint);
    }
}

Testing Integration

const testRun = testController.createTestRun(request);

const debugConfig: vscode.DebugConfiguration = {
    type: 'node',
    name: 'Debug Test',
    request: 'launch',
    program: testFile
};

await vscode.debug.startDebugging(
    workspaceFolder,
    debugConfig,
    {
        testRun: testRun,
        suppressDebugView: true
    }
);

Common Patterns

Dynamic Debug Configurations

vscode.debug.registerDebugConfigurationProvider(
    'python',
    {
        async provideDebugConfigurations(folder, token) {
            // Generate configurations dynamically
            const configs: vscode.DebugConfiguration[] = [];
            
            // Find Python files
            const files = await vscode.workspace.findFiles('**/*.py', null, 10);
            
            for (const file of files) {
                configs.push({
                    type: 'python',
                    name: `Debug ${path.basename(file.fsPath)}`,
                    request: 'launch',
                    program: file.fsPath
                });
            }
            
            return configs;
        }
    },
    vscode.DebugConfigurationProviderTriggerKind.Dynamic
);

Auto-attach Debugger

vscode.workspace.onDidOpenTextDocument(async document => {
    if (document.languageId === 'javascript' && shouldAutoDebug(document)) {
        await vscode.debug.startDebugging(undefined, {
            type: 'node',
            name: 'Auto Debug',
            request: 'launch',
            program: document.uri.fsPath
        });
    }
});

Best Practices

  • Provide sensible default configurations
  • Validate configurations before starting
  • Support both launch and attach modes
  • Document all configuration properties
  • Use debug adapter server for better performance
  • Cache debug adapter instances when possible
  • Implement efficient variable evaluation
  • Handle large data sets appropriately
  • Provide clear error messages
  • Support variable substitution in configurations
  • Implement proper breakpoint handling
  • Show meaningful debug console output

Resources