From 4306e555bb1ef93b4da8cf6a9a592dbe496b45f6 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 22 Oct 2025 02:33:08 +0800 Subject: [PATCH] update --- src/components/DebugEditor.vue | 198 +++++++++++++-- src/components/FloatingPanel.vue | 198 +++++++++++++++ src/desktop/DebugSection.vue | 415 +++++++++++++++++++++++++++++-- src/main.ts | 14 ++ 4 files changed, 785 insertions(+), 40 deletions(-) create mode 100644 src/components/FloatingPanel.vue diff --git a/src/components/DebugEditor.vue b/src/components/DebugEditor.vue index 37e4476..1789096 100644 --- a/src/components/DebugEditor.vue +++ b/src/components/DebugEditor.vue @@ -2,8 +2,8 @@ import { cpp } from "@codemirror/lang-cpp" import { python } from "@codemirror/lang-python" import { EditorState } from "@codemirror/state" -import { EditorView } from "@codemirror/view" -import { Icon } from "@iconify/vue" +import { EditorView, Decoration, DecorationSet } from "@codemirror/view" +import { StateField, StateEffect } from "@codemirror/state" import { useDark } from "@vueuse/core" import { computed, ref, watch } from "vue" import { Codemirror } from "vue-codemirror" @@ -19,6 +19,10 @@ interface Props { fontSize?: number readonly?: boolean placeholder?: string + currentLine?: number + nextLine?: number + currentLineText?: string + nextLineText?: string } const props = withDefaults(defineProps(), { @@ -31,6 +35,92 @@ const props = withDefaults(defineProps(), { const code = ref(props.modelValue) const isDark = useDark() +const editorView = ref() + +// 定义高亮效果 +const setHighlight = StateEffect.define<{ + currentLine?: number + nextLine?: number + currentLineText?: string + nextLineText?: string +}>() + +// 高亮状态字段 +const highlightField = StateField.define({ + create() { + return Decoration.none + }, + update(decorations, tr) { + decorations = decorations.map(tr.changes) + for (let effect of tr.effects) { + if (effect.is(setHighlight)) { + decorations = Decoration.none + if (effect.value.currentLine || effect.value.nextLine) { + const decorations_array: any[] = [] + + // 当前行高亮(绿色) + if (effect.value.currentLine) { + try { + const line = tr.state.doc.line(effect.value.currentLine) + decorations_array.push( + Decoration.line({ + class: "cm-current-line", + }).range(line.from), + ) + + // 在当前行添加文字 - 使用行装饰而不是Widget + if (effect.value.currentLineText) { + decorations_array.push( + Decoration.line({ + class: "cm-current-line-with-text", + attributes: { + "data-text": effect.value.currentLineText + } + }).range(line.from), + ) + } + } catch (e) { + console.warn("Invalid line number for current line:", effect.value.currentLine) + } + } + + // 下一步行高亮(红色) + if (effect.value.nextLine) { + try { + const line = tr.state.doc.line(effect.value.nextLine) + decorations_array.push( + Decoration.line({ + class: "cm-next-line", + }).range(line.from), + ) + + // 在下一步行添加文字 - 使用行装饰而不是Widget + if (effect.value.nextLineText) { + decorations_array.push( + Decoration.line({ + class: "cm-next-line-with-text", + attributes: { + "data-text": effect.value.nextLineText + } + }).range(line.from), + ) + } + } catch (e) { + console.warn("Invalid line number for next line:", effect.value.nextLine) + } + } + + // 确保装饰按位置排序,避免重复 + decorations_array.sort((a, b) => a.from - b.from) + decorations = Decoration.set(decorations_array) + } + } + } + return decorations + }, + provide: (f) => EditorView.decorations.from(f), +}) + const styleTheme = EditorView.baseTheme({ "& .cm-scroller": { "font-family": "Monaco", @@ -41,6 +131,54 @@ const styleTheme = EditorView.baseTheme({ "&.cm-editor .cm-tooltip.cm-tooltip-autocomplete ul": { "font-family": "Monaco", }, + // 当前行高亮样式(绿色) + "& .cm-current-line": { + "background-color": "rgba(0, 255, 0, 0.2)", + "border-left": "3px solid #00ff00", + }, + // 下一步行高亮样式(红色) + "& .cm-next-line": { + "background-color": "rgba(255, 0, 0, 0.2)", + "border-left": "3px solid #ff0000", + }, + // 当前行带文字样式 + "& .cm-current-line-with-text": { + position: "relative", + "&::after": { + content: "attr(data-text)", + position: "absolute", + right: "8px", + top: "50%", + transform: "translateY(-50%)", + "background-color": "rgba(0, 255, 0, 0.8)", + color: "#000", + padding: "2px 6px", + "border-radius": "3px", + "font-size": "12px", + "font-weight": "bold", + "white-space": "nowrap", + "z-index": "10", + }, + }, + // 下一步行带文字样式 + "& .cm-next-line-with-text": { + position: "relative", + "&::after": { + content: "attr(data-text)", + position: "absolute", + right: "8px", + top: "50%", + transform: "translateY(-50%)", + "background-color": "rgba(255, 0, 0, 0.8)", + color: "#fff", + padding: "2px 6px", + "border-radius": "3px", + "font-size": "12px", + "font-weight": "bold", + "white-space": "nowrap", + "z-index": "10", + }, + }, }) const emit = defineEmits(["update:modelValue", "ready"]) @@ -67,37 +205,59 @@ function onReady(payload: { state: EditorState container: HTMLDivElement }) { + editorView.value = payload.view emit("ready", payload.view) + + // Editor 准备好后立即设置高亮 + updateHighlight() } + +// 更新高亮的函数 +function updateHighlight() { + if (editorView.value) { + console.log( + "Updating highlight - currentLine:", + props.currentLine, + "nextLine:", + props.nextLine, + ) + + // 如果当前行和下一步相同,只高亮当前行,不显示下一步 + const nextLine = + props.currentLine === props.nextLine ? undefined : props.nextLine + + editorView.value.dispatch({ + effects: setHighlight.of({ + currentLine: props.currentLine, + nextLine: nextLine, + currentLineText: props.currentLineText, + nextLineText: props.nextLineText, + }), + }) + } +} + +// 监听 props 变化并更新高亮 +watch( + () => [props.currentLine, props.nextLine, props.currentLineText, props.nextLineText], + () => { + updateHighlight() + }, +) - diff --git a/src/components/FloatingPanel.vue b/src/components/FloatingPanel.vue new file mode 100644 index 0000000..dcb35a0 --- /dev/null +++ b/src/components/FloatingPanel.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/desktop/DebugSection.vue b/src/desktop/DebugSection.vue index 1345e48..e26b55e 100644 --- a/src/desktop/DebugSection.vue +++ b/src/desktop/DebugSection.vue @@ -2,10 +2,141 @@ import copyTextToClipboard from "copy-text-to-clipboard" import { useMessage } from "naive-ui" import DebugEditor from "../components/DebugEditor.vue" -import { code, input, reset, size } from "../composables/code" +import FloatingPanel from "../components/FloatingPanel.vue" +import { code, input, reset, size, output } from "../composables/code" import { debug } from "../api" +import { ref, computed } from "vue" +import { Icon } from "@iconify/vue" const message = useMessage() +const showDebug = ref(false) +const debugData = ref(null) +const currentStep = ref(0) +const showFloatingPanel = ref(false) +const isAutoRunning = ref(false) +const autoRunInterval = ref(null) + +// 计算当前行和下一步行 +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 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 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 +}) + +// 检测是否为最后一步且事件为 raw_input +const isLastStepRawInput = computed(() => { + if ( + debugData.value && + debugData.value.trace && + debugData.value.trace.length > 0 + ) { + const lastStep = debugData.value.trace[debugData.value.trace.length - 1] + return lastStep.event === "raw_input" + } + return false +}) + +// 获取事件类型的中文描述 +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[currentStep.value] + ) { + return debugData.value.trace[currentStep.value].stdout || "" + } + return output.value || "" +}) function copy() { copyTextToClipboard(code.value) @@ -13,31 +144,273 @@ function copy() { } async function handleDebug() { + showDebug.value = true const inputs = input.value ? input.value.split("\n") : [] const res = await debug(code.value, inputs) - console.log(res.data) + debugData.value = res.data + currentStep.value = 0 + + // 检查步骤数量并显示提醒 + if (res.data.trace && res.data.trace.length > 5000) { + message.warning(`超过 5000 步,请优化代码或减少循环次数`) + } + + // 检查最后一步是否为 raw_input 事件 + if (res.data.trace && res.data.trace.length > 0) { + const lastStep = res.data.trace[res.data.trace.length - 1] + if (lastStep.event === "raw_input") { + message.info("程序正在等待输入,请在输入框输入内容后重新点击调试按钮") + } + } + + // 显示前几个 trace 条目的行号 + if (res.data.trace) { + console.log("First few trace entries:") + res.data.trace.slice(0, 5).forEach((entry: any, index: number) => { + console.log(` Step ${index}: line ${entry.line}, event: ${entry.event}`) + }) + } +} + +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 + + // 如果最后一步是 raw_input,显示提醒 + const lastStep = debugData.value.trace[debugData.value.trace.length - 1] + if (lastStep.event === "raw_input") { + message.info("程序正在等待输入,请在输入区域输入内容后重新调试") + } + } +} + +function closeFloatingPanel() { + showFloatingPanel.value = false +} + +function closeDebug() { + showDebug.value = false + showFloatingPanel.value = false + debugData.value = null + currentStep.value = 0 + + // 停止自动运行 + if (autoRunInterval.value) { + clearInterval(autoRunInterval.value) + autoRunInterval.value = null + } + isAutoRunning.value = false +} + +function autoRun() { + if (!debugData.value || !debugData.value.trace) return + + if (isAutoRunning.value) { + // 停止自动运行 + if (autoRunInterval.value) { + clearInterval(autoRunInterval.value) + autoRunInterval.value = null + } + isAutoRunning.value = false + } else { + // 开始自动运行 + isAutoRunning.value = true + autoRunInterval.value = setInterval(() => { + if (currentStep.value < debugData.value.trace.length - 1) { + currentStep.value++ + } else { + // 到达最后一步,停止自动运行 + if (autoRunInterval.value) { + clearInterval(autoRunInterval.value) + autoRunInterval.value = null + } + isAutoRunning.value = false + } + }, 500) // 每500毫秒执行一步 + } }