Debugger API
Debugger APIは、Traeでアプリケーションのデバッグとデバッグセッションの管理機能を提供します。
概要
Debugger APIでは以下のことができます:
- デバッグセッションの開始と停止
- ブレークポイントの設定と管理
- コード実行のステップ実行
- 変数とコールスタックの検査
- デバッグコンテキストでの式の評価
- デバッグイベントと通知の処理
- デバッグアダプターとプロトコルの設定
- デバッグコンソール出力の管理
基本的な使用方法
デバッグセッションマネージャー
typescript
import { TraeAPI } from '@trae/api';
// デバッグセッションマネージャー
class DebugSessionManager {
private sessions: Map<string, TraeAPI.DebugSession> = new Map();
private breakpoints: Map<string, TraeAPI.Breakpoint[]> = new Map();
private eventEmitter = new TraeAPI.EventEmitter<DebugEvent>();
private disposables: TraeAPI.Disposable[] = [];
private debugAdapters: Map<string, DebugAdapterDescriptor> = new Map();
private activeSession: TraeAPI.DebugSession | undefined;
private debugConsole: DebugConsole;
constructor() {
this.debugConsole = new DebugConsole();
this.setupBuiltinAdapters();
this.setupEventHandlers();
}
// デバッグセッションの開始
async startDebugging(
folder: TraeAPI.WorkspaceFolder | undefined,
nameOrConfiguration: string | TraeAPI.DebugConfiguration,
parentSessionOrOptions?: TraeAPI.DebugSession | TraeAPI.DebugSessionOptions
): Promise<boolean> {
try {
let config: TraeAPI.DebugConfiguration;
if (typeof nameOrConfiguration === 'string') {
// 名前で設定を取得
const configs = TraeAPI.workspace.getConfiguration('launch', folder?.uri);
const launchConfig = configs.get('configurations', []);
const foundConfig = launchConfig.find((c: any) => c.name === nameOrConfiguration);
if (!foundConfig) {
throw new Error(`デバッグ設定 '${nameOrConfiguration}' が見つかりません`);
}
config = foundConfig;
} else {
config = nameOrConfiguration;
}
// 設定の解決
const resolvedConfig = await this.resolveDebugConfiguration(folder, config);
if (!resolvedConfig) {
throw new Error('デバッグ設定の解決に失敗しました');
}
// デバッグセッションの作成
const sessionId = this.generateSessionId();
const session = await this.createDebugSession(sessionId, resolvedConfig, folder);
this.sessions.set(sessionId, session);
this.activeSession = session;
// セッションの開始
await session.start();
this.eventEmitter.fire({ type: 'sessionStarted', session });
return true;
} catch (error) {
this.eventEmitter.fire({ type: 'sessionStartError', error });
TraeAPI.window.showErrorMessage(`デバッグの開始に失敗しました: ${error}`);
return false;
}
}
// デバッグセッションの停止
async stopDebugging(session?: TraeAPI.DebugSession): Promise<void> {
const targetSession = session || this.activeSession;
if (!targetSession) {
return;
}
try {
await targetSession.stop();
this.sessions.delete(targetSession.id);
if (this.activeSession === targetSession) {
this.activeSession = undefined;
}
this.eventEmitter.fire({ type: 'sessionStopped', session: targetSession });
} catch (error) {
this.eventEmitter.fire({ type: 'sessionStopError', session: targetSession, error });
throw error;
}
}
// ブレークポイント管理
async addBreakpoints(uri: TraeAPI.Uri, breakpoints: TraeAPI.SourceBreakpoint[]): Promise<TraeAPI.Breakpoint[]> {
const uriString = uri.toString();
const existingBreakpoints = this.breakpoints.get(uriString) || [];
const newBreakpoints: TraeAPI.Breakpoint[] = [];
for (const bp of breakpoints) {
const breakpoint: TraeAPI.Breakpoint = {
id: this.generateBreakpointId(),
enabled: true,
condition: bp.condition,
hitCondition: bp.hitCondition,
logMessage: bp.logMessage,
location: new TraeAPI.Location(uri, new TraeAPI.Position(bp.line - 1, 0))
};
newBreakpoints.push(breakpoint);
}
const allBreakpoints = [...existingBreakpoints, ...newBreakpoints];
this.breakpoints.set(uriString, allBreakpoints);
// アクティブセッションのブレークポイントを更新
if (this.activeSession) {
await this.updateSessionBreakpoints(this.activeSession, uri, allBreakpoints);
}
this.eventEmitter.fire({ type: 'breakpointsAdded', uri, breakpoints: newBreakpoints });
return newBreakpoints;
}
async removeBreakpoints(uri: TraeAPI.Uri, breakpoints: TraeAPI.Breakpoint[]): Promise<void> {
const uriString = uri.toString();
const existingBreakpoints = this.breakpoints.get(uriString) || [];
const breakpointIds = new Set(breakpoints.map(bp => bp.id));
const remainingBreakpoints = existingBreakpoints.filter(bp => !breakpointIds.has(bp.id));
this.breakpoints.set(uriString, remainingBreakpoints);
// アクティブセッションのブレークポイントを更新
if (this.activeSession) {
await this.updateSessionBreakpoints(this.activeSession, uri, remainingBreakpoints);
}
this.eventEmitter.fire({ type: 'breakpointsRemoved', uri, breakpoints });
}
async toggleBreakpoint(uri: TraeAPI.Uri, line: number): Promise<void> {
const uriString = uri.toString();
const breakpoints = this.breakpoints.get(uriString) || [];
const existingIndex = breakpoints.findIndex(bp =>
bp.location.range.start.line === line - 1
);
if (existingIndex >= 0) {
// 既存のブレークポイントを削除
const removed = breakpoints.splice(existingIndex, 1);
await this.removeBreakpoints(uri, removed);
} else {
// 新しいブレークポイントを追加
await this.addBreakpoints(uri, [{ line }]);
}
}
getBreakpoints(uri?: TraeAPI.Uri): TraeAPI.Breakpoint[] {
if (uri) {
return this.breakpoints.get(uri.toString()) || [];
}
const allBreakpoints: TraeAPI.Breakpoint[] = [];
for (const breakpoints of this.breakpoints.values()) {
allBreakpoints.push(...breakpoints);
}
return allBreakpoints;
}
// デバッグ実行制御
async continue(session?: TraeAPI.DebugSession): Promise<void> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
await targetSession.customRequest('continue', {
threadId: targetSession.threadId
});
this.eventEmitter.fire({ type: 'continued', session: targetSession });
}
async stepOver(session?: TraeAPI.DebugSession): Promise<void> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
await targetSession.customRequest('next', {
threadId: targetSession.threadId
});
this.eventEmitter.fire({ type: 'stepOver', session: targetSession });
}
async stepInto(session?: TraeAPI.DebugSession): Promise<void> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
await targetSession.customRequest('stepIn', {
threadId: targetSession.threadId
});
this.eventEmitter.fire({ type: 'stepInto', session: targetSession });
}
async stepOut(session?: TraeAPI.DebugSession): Promise<void> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
await targetSession.customRequest('stepOut', {
threadId: targetSession.threadId
});
this.eventEmitter.fire({ type: 'stepOut', session: targetSession });
}
async pause(session?: TraeAPI.DebugSession): Promise<void> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
await targetSession.customRequest('pause', {
threadId: targetSession.threadId
});
this.eventEmitter.fire({ type: 'paused', session: targetSession });
}
// 変数の検査
async getVariables(session?: TraeAPI.DebugSession): Promise<DebugVariable[]> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
try {
const stackTrace = await targetSession.customRequest('stackTrace', {
threadId: targetSession.threadId
});
if (!stackTrace.stackFrames || stackTrace.stackFrames.length === 0) {
return [];
}
const topFrame = stackTrace.stackFrames[0];
const scopes = await targetSession.customRequest('scopes', {
frameId: topFrame.id
});
const variables: DebugVariable[] = [];
for (const scope of scopes.scopes) {
const scopeVariables = await targetSession.customRequest('variables', {
variablesReference: scope.variablesReference
});
for (const variable of scopeVariables.variables) {
variables.push({
name: variable.name,
value: variable.value,
type: variable.type,
scope: scope.name,
variablesReference: variable.variablesReference
});
}
}
return variables;
} catch (error) {
this.eventEmitter.fire({ type: 'variableInspectionError', session: targetSession, error });
throw error;
}
}
async evaluateExpression(
expression: string,
context: 'watch' | 'repl' | 'hover' = 'repl',
session?: TraeAPI.DebugSession
): Promise<DebugEvaluationResult> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
try {
const result = await targetSession.customRequest('evaluate', {
expression,
context,
frameId: targetSession.frameId
});
return {
result: result.result,
type: result.type,
variablesReference: result.variablesReference,
namedVariables: result.namedVariables,
indexedVariables: result.indexedVariables
};
} catch (error) {
this.eventEmitter.fire({ type: 'evaluationError', expression, error });
throw error;
}
}
// コールスタック
async getCallStack(session?: TraeAPI.DebugSession): Promise<TraeAPI.StackFrame[]> {
const targetSession = session || this.activeSession;
if (!targetSession) {
throw new Error('アクティブなデバッグセッションがありません');
}
try {
const stackTrace = await targetSession.customRequest('stackTrace', {
threadId: targetSession.threadId
});
return stackTrace.stackFrames || [];
} catch (error) {
this.eventEmitter.fire({ type: 'callStackError', session: targetSession, error });
throw error;
}
}
// デバッグアダプター管理
registerDebugAdapterDescriptorFactory(
debugType: string,
factory: TraeAPI.DebugAdapterDescriptorFactory
): TraeAPI.Disposable {
this.debugAdapters.set(debugType, { factory, type: debugType });
this.eventEmitter.fire({ type: 'adapterRegistered', debugType });
return {
dispose: () => {
this.debugAdapters.delete(debugType);
this.eventEmitter.fire({ type: 'adapterUnregistered', debugType });
}
};
}
// デバッグ設定
private async resolveDebugConfiguration(
folder: TraeAPI.WorkspaceFolder | undefined,
config: TraeAPI.DebugConfiguration
): Promise<TraeAPI.DebugConfiguration | undefined> {
// 変数置換を適用
const resolvedConfig = this.substituteVariables(config, folder);
// 設定の検証
if (!resolvedConfig.type) {
throw new Error('デバッグ設定にはtypeを指定する必要があります');
}
if (!resolvedConfig.request) {
throw new Error('デバッグ設定にはrequestタイプを指定する必要があります');
}
return resolvedConfig;
}
private substituteVariables(
config: TraeAPI.DebugConfiguration,
folder: TraeAPI.WorkspaceFolder | undefined
): TraeAPI.DebugConfiguration {
const configString = JSON.stringify(config);
const workspaceRoot = folder?.uri.fsPath || TraeAPI.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
const substituted = configString
.replace(/\$\{workspaceFolder\}/g, workspaceRoot)
.replace(/\$\{workspaceRoot\}/g, workspaceRoot)
.replace(/\$\{file\}/g, TraeAPI.window.activeTextEditor?.document.uri.fsPath || '')
.replace(/\$\{fileBasename\}/g, TraeAPI.window.activeTextEditor?.document.fileName || '')
.replace(/\$\{fileDirname\}/g, TraeAPI.window.activeTextEditor?.document.uri.fsPath.split('/').slice(0, -1).join('/') || '');
return JSON.parse(substituted);
}
private async createDebugSession(
id: string,
config: TraeAPI.DebugConfiguration,
folder: TraeAPI.WorkspaceFolder | undefined
): Promise<TraeAPI.DebugSession> {
const adapter = this.debugAdapters.get(config.type);
if (!adapter) {
throw new Error(`タイプ ${config.type} のデバッグアダプターが見つかりません`);
}
// デバッグセッションの作成
const session = new DebugSessionImpl(id, config, folder, adapter);
// セッションイベントハンドラーの設定
session.onDidTerminate(() => {
this.sessions.delete(id);
if (this.activeSession === session) {
this.activeSession = undefined;
}
this.eventEmitter.fire({ type: 'sessionTerminated', session });
});
return session;
}
private async updateSessionBreakpoints(
session: TraeAPI.DebugSession,
uri: TraeAPI.Uri,
breakpoints: TraeAPI.Breakpoint[]
): Promise<void> {
try {
await session.customRequest('setBreakpoints', {
source: { path: uri.fsPath },
breakpoints: breakpoints.map(bp => ({
line: bp.location.range.start.line + 1,
condition: bp.condition,
hitCondition: bp.hitCondition,
logMessage: bp.logMessage
}))
});
} catch (error) {
console.warn('セッションブレークポイントの更新に失敗しました:', error);
}
}
private setupBuiltinAdapters(): void {
// 組み込みデバッグアダプターの登録
this.registerDebugAdapterDescriptorFactory('node', {
createDebugAdapterDescriptor: (session, executable) => {
return new TraeAPI.DebugAdapterExecutable('node', [
require.resolve('node-debug-adapter')
]);
}
});
}
private setupEventHandlers(): void {
// デバッグイベントハンドラーの設定
}
private generateSessionId(): string {
return `debug-session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private generateBreakpointId(): string {
return `breakpoint-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// ゲッター
get activeSessions(): TraeAPI.DebugSession[] {
return Array.from(this.sessions.values());
}
get activeDebugSession(): TraeAPI.DebugSession | undefined {
return this.activeSession;
}
// イベント処理
onDidStartDebugSession(listener: (session: TraeAPI.DebugSession) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'sessionStarted') {
listener(event.session!);
}
});
}
onDidTerminateDebugSession(listener: (session: TraeAPI.DebugSession) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'sessionTerminated') {
listener(event.session!);
}
});
}
onDidChangeBreakpoints(listener: (event: BreakpointEvent) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'breakpointsAdded' || event.type === 'breakpointsRemoved') {
listener(event as BreakpointEvent);
}
});
}
// リソースの解放
dispose(): void {
// すべてのセッションを停止
for (const session of this.sessions.values()) {
session.stop().catch(console.error);
}
this.sessions.clear();
// ブレークポイントをクリア
this.breakpoints.clear();
// アダプターを解放
this.debugAdapters.clear();
// その他のリソースを解放
this.disposables.forEach(d => d.dispose());
this.disposables = [];
this.eventEmitter.dispose();
this.debugConsole.dispose();
}
}
// デバッグコンソールの実装
class DebugConsole {
private outputChannel: TraeAPI.OutputChannel;
private history: string[] = [];
private maxHistorySize = 1000;
constructor() {
this.outputChannel = TraeAPI.window.createOutputChannel('Debug Console');
}
append(text: string): void {
this.outputChannel.append(text);
this.addToHistory(text);
}
appendLine(text: string): void {
this.outputChannel.appendLine(text);
this.addToHistory(text + '\n');
}
clear(): void {
this.outputChannel.clear();
this.history = [];
}
show(): void {
this.outputChannel.show();
}
private addToHistory(text: string): void {
this.history.push(text);
if (this.history.length > this.maxHistorySize) {
this.history.shift();
}
}
getHistory(): string[] {
return [...this.history];
}
dispose(): void {
this.outputChannel.dispose();
this.history = [];
}
}
// デバッグセッションの実装
class DebugSessionImpl implements TraeAPI.DebugSession {
private _onDidTerminate = new TraeAPI.EventEmitter<void>();
private _threadId = 1;
private _frameId = 0;
constructor(
public readonly id: string,
public readonly configuration: TraeAPI.DebugConfiguration,
public readonly workspaceFolder: TraeAPI.WorkspaceFolder | undefined,
private adapter: DebugAdapterDescriptor
) {}
get type(): string {
return this.configuration.type;
}
get name(): string {
return this.configuration.name;
}
get threadId(): number {
return this._threadId;
}
get frameId(): number {
return this._frameId;
}
get onDidTerminate(): TraeAPI.Event<void> {
return this._onDidTerminate.event;
}
async start(): Promise<void> {
// デバッグアダプターを開始してセッションを初期化
console.log(`デバッグセッションを開始: ${this.name}`);
}
async stop(): Promise<void> {
// デバッグセッションを停止
console.log(`デバッグセッションを停止: ${this.name}`);
this._onDidTerminate.fire();
}
async customRequest(command: string, args?: any): Promise<any> {
// デバッグアダプターにカスタムリクエストを送信
console.log(`デバッグリクエスト: ${command}`, args);
return {};
}
}
// デバッグセッションマネージャーの初期化
const debugSessionManager = new DebugSessionManager();インターフェース定義
typescript
// デバッグイベントタイプ
interface DebugEvent {
type: 'sessionStarted' | 'sessionStopped' | 'sessionTerminated' | 'sessionStartError' | 'sessionStopError' |
'breakpointsAdded' | 'breakpointsRemoved' | 'continued' | 'stepOver' | 'stepInto' | 'stepOut' | 'paused' |
'variableInspectionError' | 'evaluationError' | 'callStackError' | 'adapterRegistered' | 'adapterUnregistered';
session?: TraeAPI.DebugSession;
uri?: TraeAPI.Uri;
breakpoints?: TraeAPI.Breakpoint[];
expression?: string;
debugType?: string;
error?: any;
}
// ブレークポイントイベント
interface BreakpointEvent {
type: 'breakpointsAdded' | 'breakpointsRemoved';
uri: TraeAPI.Uri;
breakpoints: TraeAPI.Breakpoint[];
}
// デバッグ変数
interface DebugVariable {
name: string;
value: string;
type?: string;
scope: string;
variablesReference: number;
}
// デバッグ評価結果
interface DebugEvaluationResult {
result: string;
type?: string;
variablesReference: number;
namedVariables?: number;
indexedVariables?: number;
}
// デバッグアダプター記述子
interface DebugAdapterDescriptor {
factory: TraeAPI.DebugAdapterDescriptorFactory;
type: string;
}API リファレンス
DebugSessionManager
デバッグセッションの管理とブレークポイントの制御を行うメインクラスです。
メソッド
startDebugging(folder?, nameOrConfiguration, parentSessionOrOptions?)- デバッグセッションを開始stopDebugging(session?)- デバッグセッションを停止addBreakpoints(uri, breakpoints)- ブレークポイントを追加removeBreakpoints(uri, breakpoints)- ブレークポイントを削除toggleBreakpoint(uri, line)- ブレークポイントの切り替えgetBreakpoints(uri?)- ブレークポイントの取得continue(session?)- 実行を継続stepOver(session?)- ステップオーバーstepInto(session?)- ステップインstepOut(session?)- ステップアウトpause(session?)- 実行を一時停止getVariables(session?)- 変数の取得evaluateExpression(expression, context?, session?)- 式の評価getCallStack(session?)- コールスタックの取得registerDebugAdapterDescriptorFactory(debugType, factory)- デバッグアダプターファクトリーの登録
プロパティ
activeSessions- アクティブなセッションの配列activeDebugSession- 現在アクティブなデバッグセッション
イベント
onDidStartDebugSession(listener)- デバッグセッション開始イベントonDidTerminateDebugSession(listener)- デバッグセッション終了イベントonDidChangeBreakpoints(listener)- ブレークポイント変更イベント
DebugConsole
デバッグコンソールの出力を管理するクラスです。
メソッド
append(text)- テキストを追加appendLine(text)- 行を追加clear()- コンソールをクリアshow()- コンソールを表示getHistory()- 履歴を取得
最佳実践
デバッグ設定の管理
typescript
// launch.json設定の例
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/src/index.js",
"args": ["--verbose"],
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal",
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}エラー処理
typescript
try {
await debugSessionManager.startDebugging(workspaceFolder, 'Launch Program');
} catch (error) {
TraeAPI.window.showErrorMessage(`デバッグの開始に失敗: ${error.message}`);
}リソース管理
typescript
// 拡張機能の非アクティブ化時にリソースを適切に解放
export function deactivate() {
debugSessionManager.dispose();
}関連API
- Debugging API - 包括的なデバッグ機能
- Commands API - コマンドの登録と実行
- Editor API - エディターの制御
- Workspace API - ワークスペースの管理