代码编辑器调试器设计完全指南

编辑器功能调试器

调试器是代码编辑器的核心功能之一,它让开发者能够在代码执行过程中检查变量值、控制程序流程、定位问题根源。本文详细介绍断点管理、变量检查、调用栈分析、断点条件与调试控制的设计与实现。

调试器架构

整体架构设计

/* 调试器架构 */
class Debugger {
    constructor(editor) {
        this.editor = editor;
        this.breakpoints = new Map();     // 断点集合
        this.debugSession = null;        // 当前调试会话
        this.callStack = [];               // 调用栈
        this.variables = new Map();         // 当前变量
        this.status = 'idle';
    }
}
/* 调试类型定义 */
interface Breakpoint {
    id: string;
    source: string;           // 文件路径
    line: number;             // 行号
    column?: number;          // 列号
    condition?: string;       // 条件断点
    logMessage?: string;     // 日志消息
    enabled: boolean;
    hitCondition?: string;   // 命中条件
}

interface DebugSession {
    id: string;
    type: 'node' | 'browser' | 'native';
    status: 'running' | 'paused' | 'stopped';
    currentFrame: StackFrame;
}

断点管理

断点数据模型

// 断点管理器
class BreakpointManager {
    constructor() {
        this.breakpoints = new Map();  // source -> Breakpoint[]
        this.breakpointIdCounter = 0;
    }

    addBreakpoint(source, line, options = {}) {
        const breakpoint = {
            id: ++this.breakpointIdCounter,
            source,
            line,
            column: options.column || 0,
            condition: options.condition,
            logMessage: options.logMessage,
            enabled: options.enabled !== false,
            hitCondition: options.hitCondition,
            hitCount: 0
        };

        if (!this.breakpoints.has(source)) {
            this.breakpoints.set(source, []);
        }
        this.breakpoints.get(source).push(breakpoint);

        this.updateEditorDecorations();
        return breakpoint;
    }

    removeBreakpoint(id) {
        for (const [source, bps] of this.breakpoints) {
            const index = bps.findIndex(bp => bp.id === id);
            if (index !== -1) {
                bps.splice(index, 1);
                this.updateEditorDecorations();
                return true;
            }
        }
        return false;
    }

    getBreakpointsForSource(source) {
        return this.breakpoints.get(source) || [];
    }

    toggleBreakpoint(source, line) {
        const bps = this.getBreakpointsForSource(source);
        const existing = bps.find(bp => bp.line === line);

        if (existing) {
            this.removeBreakpoint(existing.id);
            return null;
        } else {
            return this.addBreakpoint(source, line);
        }
    }
}

断点装饰器

// 断点装饰器 - 在编辑器中显示断点
class BreakpointDecoration {
    constructor(editor) {
        this.editor = editor;
        this.decorations = new Map();
    }

    updateEditorDecorations() {
        const decorations = [];

        for (const [source, bps] of breakpointManager.breakpoints) {
            for (const bp of bps) {
                if (!bp.enabled) continue;

                const decoration = {
                    range: new Range(bp.line, 0, bp.line, 1),
                    options: {
                        isWholeLine: true,
                        className: 'debug-breakpoint',
                        glyphMarginClassName: 'debug-breakpoint-glyph',
                        glyphMarginHoverMessage: `断点: 第 ${bp.line} 行`
                    }
                };

                if (bp.condition) {
                    decoration.options.glyphMarginHoverMessage += `\n条件: ${bp.condition}`;
                }

                decorations.push(decoration);
            }
        }

        this.editor.deltaDecorations(this.decorations, decorations);
    }
}
/* 断点装饰器CSS样式 */
.debug-breakpoint {
    background: rgba(239, 68, 68, .15);
}

.debug-breakpoint-glyph::before {
    content: '';
    display: block;
    width: 10px;
    height: 10px;
    background: var(--accent);
    border-radius: 50%;
    margin: 5px;
}

.debug-breakpoint-log::before {
    background: #fbbf24;  // 黄色日志断点
}

.debug-breakpoint-conditional::before {
    background: #a78bfa;  // 紫色条件断点
}

条件断点与日志断点

条件断点实现

// 条件断点评估
class ConditionalBreakpoint {
    evaluate(condition, scope) {
        try {
            // 创建安全的求值函数
            const keys = Object.keys(scope);
            const values = Object.values(scope);
            const fn = new Function(...keys, `return ${condition}`);
            return fn(...values);
        } catch (e) {
            console.error('条件断点求值失败:', e);
            return false;
        }
    }

    checkHitCondition(breakpoint) {
        if (!breakpoint.hitCondition) return true;

        const bp = breakpoint;
        bp.hitCount++;

        // 解析命中条件: >n, ==n, %n
        const match = bp.hitCondition.match(/^(>|==|%)(\d+)$/);
        if (!match) return true;

        const [, op, count] = match;
        const n = parseInt(count);

        switch (op) {
            case '>': return bp.hitCount > n;
            case '==': return bp.hitCount === n;
            case '%': return bp.hitCount % n === 0;
        }

        return true;
    }
}

日志断点

// 日志断点 - 不暂停程序,只输出日志
class Logpoint {
    constructor(message, scope) {
        this.message = message;
        this.scope = scope;
    }

    evaluate() {
        // 替换消息中的占位符 {变量名}
        let result = this.message;
        for (const [key, value] of Object.entries(this.scope)) {
            const placeholder = `{${key}}`;
            if (result.includes(placeholder)) {
                result = result.replace(placeholder, this.formatValue(value));
            }
        }
        return result;
    }

    formatValue(value) {
        if (typeof value === 'object') {
            return JSON.stringify(value, null, 2);
        }
        return String(value);
    }

    log(outputPanel) {
        const message = this.evaluate();
        outputPanel.append(`[Logpoint] ${message}`);
    }
}

变量检查与监视

变量作用域

// 变量作用域管理
class ScopeManager {
    constructor() {
        this.scopes = [];
    }

    buildScopeChain(debugStop) {
        const scopes = [];

        // 全局作用域
        scopes.push({
            name: 'Global',
            type: 'global',
            variables: this.getGlobalVariables()
        });

        // 闭包作用域
        for (const closure of debugStop.closures) {
            scopes.push({
                name: closure.name || 'Closure',
                type: 'closure',
                variables: closure.variables
            });
        }

        // 局部作用域
        scopes.push({
            name: 'Local',
            type: 'local',
            variables: debugStop.localVariables
        });

        // 块级作用域
        for (const block of debugStop.blockScopes) {
            scopes.push({
                name: block.name,
                type: block.type,
                variables: block.variables
            });
        }

        this.scopes = scopes;
        return scopes;
    }

    resolveVariable(name) {
        for (const scope of this.scopes) {
            if (scope.variables.has(name)) {
                return { scope: scope.name, value: scope.variables.get(name) };
            }
        }
        return null;
    }
}

变量视图渲染

// 变量视图渲染
class VariablesView {
    constructor(container) {
        this.container = container;
    }

    render(scopes) {
        let html = '';

        for (const scope of scopes) {
            html += ` class="scope-group">`;
            html += ` class="scope-header" data-expanded="true">`;
            html += ` class="icon">▼`;
            html += `${scope.name}`;
            html += `${scope.type}`;
            html += `
`; html += ` class="scope-variables">`; for (const [name, value] of Object.entries(scope.variables)) { html += this.renderVariable(name, value); } html += ``; html += ``; } this.container.innerHTML = html; this.attachEvents(); } renderVariable(name, value) { const type = this.getType(value); const displayValue = this.formatValue(value); const expandable = this.isExpandable(value); return ` class="variable ${expandable ? 'expandable' : ''}" data-name="${name}"> class="var-name">${name} class="var-type">${type} class="var-value">${displayValue} `; } isExpandable(value) { return typeof value === 'object' && value !== null; } }

调用栈

调用栈管理

// 调用栈管理
class CallStackManager {
    constructor() {
        this.frames = [];
        this.selectedFrameId = 0;
    }

    updateFrames(stackFrames) {
        this.frames = stackFrames.map((frame, index) => ({
            id: index,
            name: frame.functionName,
            source: frame.source,
            line: frame.line,
            column: frame.column,
            scope: frame.scope
        }));
    }

    getFrameAt(index) {
        return this.frames[index];
    }

    selectFrame(index) {
        if (index >= 0 && index < this.frames.length) {
            this.selectedFrameId = index;
            this.emitFrameChanged();
        }
    }

    getCurrentFrame() {
        return this.frames[this.selectedFrameId];
    }
}

调用栈视图

// 调用栈视图渲染
class CallStackView {
    render(frames, selectedId) {
        const html = frames.map((frame, index) => {
            const isSelected = index === selectedId;
            const location = `${frame.source}:${frame.line}:${frame.column}`;

            return `
                 class="stack-frame ${isSelected ? 'selected' : ''}" data-frame-id="${index}">
                     class="frame-index">${index}
                     class="frame-name">${frame.name || '(anonymous)'}
                     class="frame-location">${location}
                
            `;
        }).join('');

        this.container.innerHTML = html;
    }
}

调试控制

调试操作

// 调试控制器
class DebugController {
    constructor(debugger) {
        this.debugger = debugger;
    }

    start() {
        this.debugger.status = 'running';
        this.debugger.sendCommand('launch');
    }

    pause() {
        this.debugger.sendCommand('pause');
    }

    continue() {
        this.debugger.status = 'running';
        this.debugger.sendCommand('continue');
    }

    stepOver() {
        this.debugger.status = 'paused';
        this.debugger.sendCommand('next');
    }

    stepInto() {
        this.debugger.status = 'paused';
        this.debugger.sendCommand('stepIn');
    }

    stepOut() {
        this.debugger.status = 'paused';
        this.debugger.sendCommand('stepOut');
    }

    stop() {
        this.debugger.status = 'stopped';
        this.debugger.sendCommand('disconnect');
    }
}

调试UI面板

// 调试工具栏
class DebugToolbar {
    render(status) {
        const canContinue = status === 'paused';
        const canPause = status === 'running';

        return `
             class="debug-toolbar">
                 class="btn-start" title="启动调试">
                    ▶开始
                
                 class="btn-pause" ${canPause ? '' : 'disabled'} title="暂停">
                    ⏸暂停
                
                 class="btn-continue" ${canContinue ? '' : 'disabled'} title="继续">
                    ▶继续
                
                 class="btn-stepover" ${canContinue ? '' : 'disabled'} title="单步跳过">
                    ⏭跳过
                
                 class="btn-stepin" ${canContinue ? '' : 'disabled'} title="单步进入">
                    ⏭进入
                
                 class="btn-stepout" ${canContinue ? '' : 'disabled'} title="单步退出">
                    ⏮退出
                
                 class="btn-stop" title="停止调试">
                    ⏹停止
                
            
        `;
    }
}

调试控制台集成

REPL控制台

// 调试REPL控制台
class DebugConsole {
    constructor(scopeManager) {
        this.scopeManager = scopeManager;
    }

    execute(input) {
        try {
            // 获取当前作用域的所有变量
            const scopeVars = {};
            for (const scope of this.scopeManager.scopes) {
                Object.assign(scopeVars, scope.variables);
            }

            // 执行表达式
            const keys = Object.keys(scopeVars);
            const values = Object.values(scopeVars);
            const fn = new Function(...keys, `return ${input}`);
            const result = fn(...values);

            return { success: true, value: this.formatResult(result) };
        } catch (e) {
            return { success: false, error: e.message };
        }
    }

    formatResult(value) {
        if (typeof value === 'object' && value !== null) {
            try {
                return JSON.stringify(value, null, 2);
            } catch {
                return String(value);
            }
        }
        return String(value);
    }
}

总结

一个功能完善的调试器能够显著提升开发效率,帮助开发者快速定位和解决问题。