From 2ad9d73c6e13c80de435ee9a91975c037ee79b98 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 25 Dec 2025 10:40:46 +0800 Subject: [PATCH] refactor debug panel --- src/{desktop => components}/AnalysisPanel.vue | 0 src/components/DebugEditor.vue | 5 +- src/components/DebugPanel.vue | 566 ++++++++++++++---- src/desktop/CodeSection.vue | 69 ++- src/desktop/Content.vue | 5 +- src/desktop/DebugSection.vue | 536 ----------------- src/desktop/OutputSection.vue | 2 +- 7 files changed, 531 insertions(+), 652 deletions(-) rename src/{desktop => components}/AnalysisPanel.vue (100%) delete mode 100644 src/desktop/DebugSection.vue diff --git a/src/desktop/AnalysisPanel.vue b/src/components/AnalysisPanel.vue similarity index 100% rename from src/desktop/AnalysisPanel.vue rename to src/components/AnalysisPanel.vue diff --git a/src/components/DebugEditor.vue b/src/components/DebugEditor.vue index e07a438..34a62f7 100644 --- a/src/components/DebugEditor.vue +++ b/src/components/DebugEditor.vue @@ -19,11 +19,13 @@ interface Props { nextLine?: number currentLineText?: string nextLineText?: string + height?: number } const props = withDefaults(defineProps(), { language: "python", fontSize: 24, + height: 600, }) const code = ref(props.modelValue) @@ -123,7 +125,6 @@ const highlightField = StateField.define({ const styleTheme = EditorView.baseTheme({ "& .cm-scroller": { "font-family": "Monaco", - height: "calc(100vh - 120px)", }, "&.cm-editor.cm-focused": { outline: "none", @@ -254,10 +255,12 @@ watch( -import { computed } from "vue" +// Vue 核心 +import { ref, computed, watch } from "vue" + +// 第三方库 +import copyTextToClipboard from "copy-text-to-clipboard" +import { useMessage } from "naive-ui" import { Icon } from "@iconify/vue" +import { useIntervalFn } from "@vueuse/core" -interface Props { - visible: boolean - variables?: Record - output?: string -} +// 组件 +import DebugEditor from "./DebugEditor.vue" -const props = withDefaults(defineProps(), { - visible: false, - variables: () => ({}), - output: "", +// 组合式函数和类型 +import { code, input, reset, size, output, status } from "../composables/code" +import { Status } from "../types" +import { debug } from "../api" + +// ==================== Props 和 Emits ==================== +const props = defineProps<{ + initialDebugData?: any +}>() + +const emit = defineEmits<{ + close: [] +}>() + +// ==================== 响应式状态 ==================== +const message = useMessage() + +// 调试状态 +const debugData = ref(props.initialDebugData || null) +const currentStep = ref(0) + +// 自动运行状态 +const isAutoRunning = ref(false) + +const { + pause: pauseAutoRun, + resume: resumeAutoRun, + isActive: isAutoRunActive, +} = useIntervalFn( + () => { + if (currentStep.value < debugData.value.trace.length - 1) { + currentStep.value++ + + // 如果遇到输入步骤,暂停自动运行 + if (debugData.value.trace[currentStep.value]?.event === "raw_input") { + pauseAutoRun() + isAutoRunning.value = false + message.info("程序正在等待输入,自动运行已暂停") + } + } else { + // 到达最后一步,停止自动运行 + pauseAutoRun() + isAutoRunning.value = false + } + }, + 500, + { immediate: false }, +) + +// ==================== 监听器 ==================== +// 监听 props 变化 +watch( + () => props.initialDebugData, + (newData) => { + if (newData) { + debugData.value = newData + currentStep.value = 0 + } + }, + { immediate: true }, +) + +// ==================== 计算属性 ==================== +// 调试行号相关 +const currentLine = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace[currentStep.value] + ) { + const line = debugData.value.trace[currentStep.value].line + console.log(`Step ${currentStep.value}: currentLine = ${line}`) + return line && line > 0 ? line : undefined + } + return undefined }) -const emit = defineEmits(["close"]) +const nextLine = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace[currentStep.value + 1] + ) { + const line = debugData.value.trace[currentStep.value + 1].line + console.log(`Step ${currentStep.value}: nextLine = ${line}`) + return line && line > 0 && line !== currentLine.value ? line : undefined + } + console.log(`Step ${currentStep.value}: nextLine = undefined (no next step)`) + return undefined +}) + +// 调试信息相关 +const currentVariables = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace[currentStep.value] + ) { + return debugData.value.trace[currentStep.value].globals || {} + } + return {} +}) // 格式化变量显示 const formattedVariables = computed(() => { - if (!props.variables || Object.keys(props.variables).length === 0) { + const variables = currentVariables.value + if (!variables || Object.keys(variables).length === 0) { return [] } - return Object.entries(props.variables).map(([key, value]) => { + return Object.entries(variables).map(([key, value]) => { // 处理特殊类型 let displayValue = "" let displayType = typeof value @@ -49,59 +148,333 @@ const formattedVariables = computed(() => { }) }) -// 格式化输出显示 -const formattedOutput = computed(() => { - if (!props.output) return "" - return props.output -}) - // 计算输出行数 const outputLines = computed(() => { - if (!props.output) return 0 - return props.output.split("\n").filter((line) => line !== "").length + const output = currentOutput.value + if (!output) return 0 + return output.split("\n").filter((line) => line !== "").length }) -function closePanel() { - emit("close") +const currentLineText = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace[currentStep.value] + ) { + const step = debugData.value.trace[currentStep.value] + const isLastStep = currentStep.value === debugData.value.trace.length - 1 + const eventText = isLastStep ? "" : getEventText(step.event) + const stepText = isLastStep + ? "最后一步" + : `当前第${currentStep.value + 1}步` + return `${stepText}${eventText}` + } + return undefined +}) + +const nextLineText = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace[currentStep.value + 1] + ) { + const step = debugData.value.trace[currentStep.value + 1] + const isNextLastStep = + currentStep.value + 1 === debugData.value.trace.length - 1 + const eventText = isNextLastStep ? "" : getEventText(step.event) + const stepText = isNextLastStep ? "最后一步" : `下一步` + return `${stepText}${eventText}` + } + return undefined +}) + +// ==================== 工具函数 ==================== +/** + * 获取事件类型的中文描述 + */ +function getEventText(event: string): string { + switch (event) { + case "step_line": + return "" // 普通执行不显示额外文字 + case "call": + return "(调用函数)" + case "return": + return "(函数返回)" + case "exception": + return "(异常)" + case "uncaught_exception": + return "(异常)" + case "raw_input": + return "(等待输入)" + default: + return event || "" + } +} + +// 输出相关 +const currentOutput = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace.length > 0 + ) { + let outputText = "" + + for (let i = 0; i <= currentStep.value; i++) { + const step = debugData.value.trace[i] + if (step) { + if (step.event === "exception" || step.event === "uncaught_exception") { + if (step.exception_msg) { + outputText = step.exception_msg + } + } else if (step.stdout) { + outputText = step.stdout + } + } + } + + outputText = outputText.trimEnd() + + const hasException = debugData.value.trace.some( + (step: any) => + step.event === "exception" || step.event === "uncaught_exception", + ) + status.value = hasException ? Status.RuntimeError : Status.Accepted + + output.value = outputText + return outputText + } + return output.value || "" +}) + +// ==================== 主要功能函数 ==================== +/** + * 复制代码到剪贴板 + */ +function copy() { + copyTextToClipboard(code.value) + message.success("已经复制好了") +} + +// 当 debugData 更新时,初始化相关状态 +watch( + () => debugData.value, + (newData) => { + if (newData) { + currentStep.value = 0 + + // 检查步骤数量并显示提醒 + if (newData.trace && newData.trace.length > 5000) { + message.warning(`超过 5000 步,请优化代码或减少循环次数`) + } + + // 显示前几个 trace 条目的行号 + if (newData.trace) { + console.log("First few trace entries:") + newData.trace.slice(0, 5).forEach((entry: any, index: number) => { + console.log( + ` Step ${index}: line ${entry.line}, event: ${entry.event}`, + ) + }) + } + } + }, + { immediate: true }, +) + +// ==================== 调试控制函数 ==================== +/** + * 跳转到第一步 + */ +function firstStep() { + if (debugData.value && debugData.value.trace) { + currentStep.value = 0 + } +} + +/** + * 上一步 + */ +function prevStep() { + if (debugData.value && debugData.value.trace && currentStep.value > 0) { + currentStep.value-- + } +} + +/** + * 下一步 + */ +function nextStep() { + if ( + debugData.value && + debugData.value.trace && + currentStep.value < debugData.value.trace.length - 1 + ) { + currentStep.value++ + } +} + +/** + * 跳转到最后一步 + */ +function lastStep() { + if (debugData.value && debugData.value.trace) { + currentStep.value = debugData.value.trace.length - 1 + } +} + +// ==================== UI控制函数 ==================== +/** + * 自动运行/暂停 + */ +function autoRun() { + if (!debugData.value || !debugData.value.trace) return + + if (isAutoRunActive.value) { + // 停止自动运行 + pauseAutoRun() + isAutoRunning.value = false + } else { + // 开始自动运行 + isAutoRunning.value = true + resumeAutoRun() + } } - - + diff --git a/src/desktop/CodeSection.vue b/src/desktop/CodeSection.vue index 82462e3..0c039bd 100644 --- a/src/desktop/CodeSection.vue +++ b/src/desktop/CodeSection.vue @@ -1,21 +1,64 @@ @@ -30,6 +73,28 @@ async function handleDebug() { + + + + diff --git a/src/desktop/Content.vue b/src/desktop/Content.vue index c4f93b5..852db90 100644 --- a/src/desktop/Content.vue +++ b/src/desktop/Content.vue @@ -1,7 +1,6 @@ - - - diff --git a/src/desktop/OutputSection.vue b/src/desktop/OutputSection.vue index 98ceacf..9aa1acb 100644 --- a/src/desktop/OutputSection.vue +++ b/src/desktop/OutputSection.vue @@ -8,7 +8,7 @@ import { getAIAnalysis, showAnalysis, } from "../composables/analysis" -import AnalysisPanel from "./AnalysisPanel.vue" +import AnalysisPanel from "../components/AnalysisPanel.vue"