refactor debug panel
Some checks failed
Deploy / build-and-deploy (push) Has been cancelled

This commit is contained in:
2025-12-25 10:40:46 +08:00
parent 5a77902750
commit 2ad9d73c6e
7 changed files with 531 additions and 652 deletions

View File

@@ -19,11 +19,13 @@ interface Props {
nextLine?: number nextLine?: number
currentLineText?: string currentLineText?: string
nextLineText?: string nextLineText?: string
height?: number
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
language: "python", language: "python",
fontSize: 24, fontSize: 24,
height: 600,
}) })
const code = ref(props.modelValue) const code = ref(props.modelValue)
@@ -123,7 +125,6 @@ const highlightField = StateField.define<DecorationSet>({
const styleTheme = EditorView.baseTheme({ const styleTheme = EditorView.baseTheme({
"& .cm-scroller": { "& .cm-scroller": {
"font-family": "Monaco", "font-family": "Monaco",
height: "calc(100vh - 120px)",
}, },
"&.cm-editor.cm-focused": { "&.cm-editor.cm-focused": {
outline: "none", outline: "none",
@@ -254,10 +255,12 @@ watch(
<Codemirror <Codemirror
v-model="code" v-model="code"
indentWithTab indentWithTab
disabled
:extensions="[styleTheme, lang, highlightField, isDark ? oneDark : smoothy]" :extensions="[styleTheme, lang, highlightField, isDark ? oneDark : smoothy]"
:tabSize="4" :tabSize="4"
:style="{ :style="{
fontSize: props.fontSize + 'px', fontSize: props.fontSize + 'px',
height: props.height + 'px',
}" }"
@change="onChange" @change="onChange"
@ready="onReady" @ready="onReady"

View File

@@ -1,28 +1,127 @@
<script lang="ts" setup> <script lang="ts" setup>
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 { Icon } from "@iconify/vue"
import { useIntervalFn } from "@vueuse/core"
interface Props { // 组件
visible: boolean import DebugEditor from "./DebugEditor.vue"
variables?: Record<string, any>
output?: string // 组合式函数和类型
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<any>(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 },
)
const props = withDefaults(defineProps<Props>(), { // ==================== 监听器 ====================
visible: false, // 监听 props 变化
variables: () => ({}), watch(
output: "", () => 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(() => { 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 []
} }
return Object.entries(props.variables).map(([key, value]) => { return Object.entries(variables).map(([key, value]) => {
// 处理特殊类型 // 处理特殊类型
let displayValue = "" let displayValue = ""
let displayType = typeof value let displayType = typeof value
@@ -49,25 +148,303 @@ const formattedVariables = computed(() => {
}) })
}) })
// 格式化输出显示
const formattedOutput = computed(() => {
if (!props.output) return ""
return props.output
})
// 计算输出行数 // 计算输出行数
const outputLines = computed(() => { const outputLines = computed(() => {
if (!props.output) return 0 const output = currentOutput.value
return props.output.split("\n").filter((line) => line !== "").length if (!output) return 0
return output.split("\n").filter((line) => line !== "").length
}) })
function closePanel() { const currentLineText = computed(() => {
emit("close") 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()
}
} }
</script> </script>
<template> <template>
<n-card v-if="visible" class="floating-panel" :bordered="true" size="small"> <n-flex>
<!-- 左侧分为上中下三层 -->
<n-flex vertical style="flex: 1">
<DebugEditor
v-model="code.value"
:font-size="size"
:language="code.language"
:current-line="currentLine"
:next-line="nextLine"
:current-line-text="currentLineText"
:next-line-text="nextLineText"
/>
<!-- 调试控制按钮 -->
<n-flex align="center" justify="center" style="margin-top: 20px">
<!-- 步骤控制 -->
<n-tooltip>
<template #trigger>
<n-button @click="firstStep">
<template #icon>
<Icon icon="material-symbols:skip-previous" />
</template>
</n-button>
</template>
第一步
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button type="primary" @click="prevStep">
<template #icon>
<Icon icon="tabler:chevron-left" />
</template>
</n-button>
</template>
上一步
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button
:type="isAutoRunning ? 'warning' : 'info'"
@click="autoRun"
>
<template #icon>
<Icon
:icon="
isAutoRunning
? 'material-symbols:pause'
: 'material-symbols:play-arrow'
"
/>
</template>
</n-button>
</template>
{{ isAutoRunning ? "暂停自动运行" : "开始自动运行" }}
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button type="error" @click="nextStep">
<template #icon>
<Icon icon="tabler:chevron-right" />
</template>
</n-button>
</template>
下一步
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button @click="lastStep">
<template #icon>
<Icon icon="material-symbols:skip-next" />
</template>
</n-button>
</template>
最后一步
</n-tooltip>
</n-flex>
<!-- 调试进度条 -->
<n-flex v-if="debugData && debugData.trace" vertical>
<n-slider
v-model:value="currentStep"
:min="0"
:max="debugData.trace.length - 1"
:step="1"
:tooltip="false"
/>
<!-- 步骤信息 -->
<n-text
depth="3"
style="text-align: center; font-size: 12px; white-space: nowrap"
>
步骤: {{ currentStep + 1 }} / {{ debugData.trace.length }}
</n-text>
</n-flex>
</n-flex>
<!-- 右侧调试信息面板 -->
<n-card :bordered="true" size="small" style="width: 350px">
<template #header> <template #header>
<n-flex justify="space-between" align="center"> <n-flex justify="space-between" align="center">
<n-flex align="center"> <n-flex align="center">
@@ -76,21 +453,12 @@ function closePanel() {
</n-icon> </n-icon>
<n-text strong>调试信息</n-text> <n-text strong>调试信息</n-text>
</n-flex> </n-flex>
<n-button quaternary circle size="small" @click="closePanel">
<template #icon>
<n-icon>
<Icon icon="mdi:close" :width="16" :height="16" />
</n-icon>
</template>
</n-button>
</n-flex> </n-flex>
</template> </template>
<n-space vertical :size="16" class="panel-content">
<!-- 变量部分 --> <!-- 变量部分 -->
<n-collapse :default-expanded-names="['variables']"> <n-collapse :default-expanded-names="['variables']">
<n-collapse-item title="变量" name="variables"> <n-collapse-item name="variables">
<n-scrollbar style="max-height: 260px">
<template #header> <template #header>
<n-flex align="center"> <n-flex align="center">
<n-icon> <n-icon>
@@ -99,9 +467,14 @@ function closePanel() {
<n-text>变量</n-text> <n-text>变量</n-text>
</n-flex> </n-flex>
</template> </template>
<div v-if="formattedVariables.length === 0"> <n-scrollbar style="max-height: 260px">
<n-text type="info" class="no-variables-text">暂无变量</n-text> <n-space
</div> v-if="formattedVariables.length === 0"
vertical
style="padding: 20px; text-align: center"
>
<n-text type="info">暂无变量</n-text>
</n-space>
<n-space v-else vertical> <n-space v-else vertical>
<n-card <n-card
v-for="variable in formattedVariables" v-for="variable in formattedVariables"
@@ -109,17 +482,22 @@ function closePanel() {
size="small" size="small"
:bordered="true" :bordered="true"
> >
<n-flex <n-space vertical :size="8">
justify="space-between" <n-flex justify="space-between" align="center">
align="center" <n-text strong type="primary">{{ variable.name }}</n-text>
class="variable-header"
>
<n-text strong :type="'primary'">{{ variable.name }}</n-text>
<n-tag size="small" type="info">{{ variable.type }}</n-tag> <n-tag size="small" type="info">{{ variable.type }}</n-tag>
</n-flex> </n-flex>
<n-text code class="variable-value"> <n-text
code
style="
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
"
>
{{ variable.value }} {{ variable.value }}
</n-text> </n-text>
</n-space>
</n-card> </n-card>
</n-space> </n-space>
</n-scrollbar> </n-scrollbar>
@@ -127,8 +505,8 @@ function closePanel() {
</n-collapse> </n-collapse>
<!-- 输出部分 --> <!-- 输出部分 -->
<n-collapse v-if="formattedOutput" :default-expanded-names="['output']"> <n-collapse v-if="currentOutput" :default-expanded-names="['output']">
<n-collapse-item :title="`输出 (${outputLines} 行)`" name="output"> <n-collapse-item name="output">
<template #header> <template #header>
<n-flex align="center"> <n-flex align="center">
<n-icon> <n-icon>
@@ -139,50 +517,22 @@ function closePanel() {
</template> </template>
<n-card size="small" :bordered="true"> <n-card size="small" :bordered="true">
<n-scrollbar style="max-height: 300px"> <n-scrollbar style="max-height: 300px">
<n-text code class="output-text"> <n-text
{{ formattedOutput }} code
style="
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
display: block;
"
>
{{ currentOutput }}
</n-text> </n-text>
</n-scrollbar> </n-scrollbar>
</n-card> </n-card>
</n-collapse-item> </n-collapse-item>
</n-collapse> </n-collapse>
</n-space>
</n-card> </n-card>
</n-flex>
</template> </template>
<style scoped></style>
<style scoped>
.floating-panel {
width: 300px;
position: absolute;
top: 20px;
right: 120px;
z-index: 100;
}
.output-text {
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
display: block;
}
.panel-content {
padding: 16px;
}
.no-variables-text {
text-align: center;
display: block;
padding: 20px;
}
.variable-header {
margin-bottom: 8px;
}
.variable-value {
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
}
</style>

View File

@@ -1,21 +1,64 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"
import copyTextToClipboard from "copy-text-to-clipboard" import copyTextToClipboard from "copy-text-to-clipboard"
import { useMessage } from "naive-ui" import { useMessage } from "naive-ui"
import CodeEditor from "../components/CodeEditor.vue" import CodeEditor from "../components/CodeEditor.vue"
import DebugPanel from "../components/DebugPanel.vue"
import { code, input, reset, size } from "../composables/code" import { code, input, reset, size } from "../composables/code"
import { debug } from "../api" import { debug } from "../api"
const message = useMessage() const message = useMessage()
const showDebugModal = ref(false)
const debugData = ref<any>(null)
function closeDebug() {
showDebugModal.value = false
debugData.value = null
}
function copy() { function copy() {
copyTextToClipboard(code.value) copyTextToClipboard(code.value)
message.success("已经复制好了") message.success("已经复制好了")
} }
/**
* 检查调试数据是否需要输入但用户没有提供足够的输入
*/
function needsInputButNotProvided(debugData: any, providedInputs: string[]): boolean {
if (!debugData?.trace || debugData.trace.length === 0) {
return false
}
const lastStep = debugData.trace[debugData.trace.length - 1]
// 如果最后一步是 raw_input说明程序在等待输入用户提供的输入不足
if (lastStep.event === "raw_input") {
// 统计 trace 中所有的 raw_input 事件数量(程序需要的输入数量)
const requiredInputCount = debugData.trace.filter(
(step: any) => step.event === "raw_input"
).length
// 如果用户提供的输入数量不足,返回 true
return providedInputs.length < requiredInputCount
}
return false
}
async function handleDebug() { async function handleDebug() {
const inputs = input.value ? input.value.split("\n") : [] const inputs = input.value
? input.value.split("\n").filter((line) => line.trim() !== "")
: []
const res = await debug(code.value, inputs) const res = await debug(code.value, inputs)
console.log(res.data) debugData.value = res.data
// 检查是否需要输入但用户没有提供足够的输入
if (needsInputButNotProvided(res.data, inputs)) {
message.warning("程序需要输入,请在输入框输入内容后重新点击调试按钮")
return
}
showDebugModal.value = true
} }
</script> </script>
@@ -30,6 +73,28 @@ async function handleDebug() {
<template #actions> <template #actions>
<n-button quaternary type="primary" @click="copy">复制</n-button> <n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button> <n-button quaternary @click="reset">清空</n-button>
<n-button
v-if="code.language === 'python'"
quaternary
type="error"
:disabled="!code.value"
@click="handleDebug"
>
调试
</n-button>
</template> </template>
</CodeEditor> </CodeEditor>
<n-modal
v-model:show="showDebugModal"
preset="card"
title="调试"
size="large"
:mask-closable="false"
:auto-focus="false"
@close="closeDebug"
style="width: 80vw; max-width: 1000px;"
>
<DebugPanel :initial-debug-data="debugData" @close="closeDebug" />
</n-modal>
</template> </template>

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { code } from "../composables/code" import { code } from "../composables/code"
import CodeSection from "./CodeSection.vue" import CodeSection from "./CodeSection.vue"
import DebugSection from "./DebugSection.vue"
import InputSection from "./InputSection.vue" import InputSection from "./InputSection.vue"
import OutputSection from "./OutputSection.vue" import OutputSection from "./OutputSection.vue"
import TurtleSection from "./TurtleSection.vue" import TurtleSection from "./TurtleSection.vue"
@@ -11,9 +10,7 @@ import TurtleSection from "./TurtleSection.vue"
<n-layout-content class="container"> <n-layout-content class="container">
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5"> <n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
<template #1> <template #1>
<component <CodeSection />
:is="code.language === 'python' ? DebugSection : CodeSection"
/>
</template> </template>
<template #2> <template #2>
<n-split <n-split

View File

@@ -1,536 +0,0 @@
<script lang="ts" setup>
// 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"
// 组件
import DebugEditor from "../components/DebugEditor.vue"
import DebugPanel from "../components/DebugPanel.vue"
// 组合式函数和类型
import { code, input, reset, size, output, status } from "../composables/code"
import { Status } from "../types"
import { debug } from "../api"
// ==================== 响应式状态 ====================
const message = useMessage()
// 调试状态
const showDebug = ref(false)
const debugData = ref<any>(null)
const currentStep = ref(0)
// UI 状态
const showFloatingPanel = ref(false)
// 自动运行状态
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 },
)
// 调试状态快照(用于检测代码/输入变化)
let debugStartCode = ""
let debugStartInput = ""
// ==================== 监听器 ====================
// 监听代码变化
watch(
() => code.value,
(newCode) => {
if (showDebug.value && newCode !== debugStartCode) {
message.warning("代码已修改,请重新点击调试按钮")
}
},
)
// 监听输入变化
watch(
() => input.value,
(newInput) => {
if (showDebug.value && newInput !== debugStartInput) {
message.warning("输入已修改,请重新点击调试按钮")
}
},
)
// ==================== 计算属性 ====================
// 调试行号相关
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
})
// ==================== 工具函数 ====================
/**
* 获取事件类型的中文描述
*/
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("已经复制好了")
}
/**
* 开始调试
*/
async function handleDebug() {
showDebug.value = true
showFloatingPanel.value = true
// 保存调试开始时的代码和输入状态
debugStartCode = code.value
debugStartInput = input.value
const inputs = input.value ? input.value.split("\n") : []
const res = await debug(code.value, inputs)
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("程序正在等待输入,请在输入区域输入内容后重新调试")
}
}
}
// ==================== UI控制函数 ====================
/**
* 关闭浮动面板
*/
function closeFloatingPanel() {
showFloatingPanel.value = false
}
/**
* 关闭调试模式
*/
function closeDebug() {
showDebug.value = false
showFloatingPanel.value = false
debugData.value = null
currentStep.value = 0
// 清除保存的调试状态
debugStartCode = ""
debugStartInput = ""
// 停止自动运行
pauseAutoRun()
isAutoRunning.value = false
}
/**
* 自动运行/暂停
*/
function autoRun() {
if (!debugData.value || !debugData.value.trace) return
if (isAutoRunActive.value) {
// 停止自动运行
pauseAutoRun()
isAutoRunning.value = false
} else {
// 开始自动运行
isAutoRunning.value = true
resumeAutoRun()
}
}
</script>
<template>
<!-- 头部工具栏 -->
<n-flex align="center" class="header">
<!-- 标题和基础操作 -->
<Icon icon="streamline-emojis:lemon" :width="24" :height="24" />
<span class="title">代码区</span>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button
quaternary
type="error"
:disabled="!code.value"
@click="handleDebug"
>
调试
</n-button>
<!-- 调试控制按钮 -->
<template v-if="showDebug">
<!-- 步骤控制 -->
<n-tooltip>
<template #trigger>
<n-button text @click="firstStep">
<template #icon>
<Icon icon="material-symbols:skip-previous" />
</template>
</n-button>
</template>
第一步
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button text type="primary" @click="prevStep">
<template #icon>
<Icon icon="tabler:chevron-left" />
</template>
</n-button>
</template>
上一步
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button
:type="isAutoRunning ? 'warning' : 'info'"
text
@click="autoRun"
>
<template #icon>
<Icon
:icon="
isAutoRunning
? 'material-symbols:pause'
: 'material-symbols:play-arrow'
"
/>
</template>
</n-button>
</template>
{{ isAutoRunning ? "暂停自动运行" : "开始自动运行" }}
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button type="error" text @click="nextStep">
<template #icon>
<Icon icon="tabler:chevron-right" />
</template>
</n-button>
</template>
下一步
</n-tooltip>
<n-tooltip>
<template #trigger>
<n-button text @click="lastStep">
<template #icon>
<Icon icon="material-symbols:skip-next" />
</template>
</n-button>
</template>
最后一步
</n-tooltip>
<!-- 面板控制 -->
<n-button
quaternary
type="info"
@click="showFloatingPanel = !showFloatingPanel"
>
{{ showFloatingPanel ? "隐藏面板" : "显示面板" }}
</n-button>
</template>
<!-- 调试进度条 -->
<n-flex
v-if="showDebug && debugData && debugData.trace"
align="center"
class="progress-section"
>
<n-slider
v-model:value="currentStep"
:min="0"
:max="debugData.trace.length - 1"
:step="1"
:tooltip="false"
class="debug-progress"
/>
<span class="progress-text">
步骤: {{ currentStep + 1 }} / {{ debugData.trace.length }}
</span>
</n-flex>
<!-- 关闭调试 -->
<n-tooltip v-if="showDebug">
<template #trigger>
<n-button text type="error" @click="closeDebug">
<template #icon>
<Icon icon="tabler:x" />
</template>
</n-button>
</template>
关闭调试
</n-tooltip>
</n-flex>
<!-- 主要内容区域 -->
<div class="debug-container">
<!-- 代码编辑器 -->
<DebugEditor
v-model="code.value"
:font-size="size"
:language="code.language"
:current-line="currentLine"
:next-line="nextLine"
:current-line-text="currentLineText"
:next-line-text="nextLineText"
/>
<!-- 调试面板 -->
<DebugPanel
:visible="showFloatingPanel"
:variables="currentVariables"
:output="currentOutput"
@close="closeFloatingPanel"
/>
</div>
</template>
<style scoped>
/* ==================== 头部样式 ==================== */
.header {
padding: 12px 20px;
height: 60px;
box-sizing: border-box;
}
.title {
font-size: 16px;
}
/* ==================== 主容器样式 ==================== */
.debug-container {
position: relative;
}
/* ==================== 进度条样式 ==================== */
.progress-section {
flex: 1;
}
.debug-progress {
flex: 1;
max-width: 200px;
}
.progress-text {
font-size: 12px;
color: var(--n-text-color-disabled);
white-space: nowrap;
}
</style>

View File

@@ -8,7 +8,7 @@ import {
getAIAnalysis, getAIAnalysis,
showAnalysis, showAnalysis,
} from "../composables/analysis" } from "../composables/analysis"
import AnalysisPanel from "./AnalysisPanel.vue" import AnalysisPanel from "../components/AnalysisPanel.vue"
</script> </script>
<template> <template>