This commit is contained in:
@@ -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<Props>(), {
|
||||
@@ -31,6 +35,92 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const code = ref(props.modelValue)
|
||||
const isDark = useDark()
|
||||
const editorView = ref<EditorView>()
|
||||
|
||||
// 定义高亮效果
|
||||
const setHighlight = StateEffect.define<{
|
||||
currentLine?: number
|
||||
nextLine?: number
|
||||
currentLineText?: string
|
||||
nextLineText?: string
|
||||
}>()
|
||||
|
||||
// 高亮状态字段
|
||||
const highlightField = StateField.define<DecorationSet>({
|
||||
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()
|
||||
},
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<n-flex align="center" class="header" v-if="props.label">
|
||||
<Icon v-if="icon" :icon="icon" :width="24" :height="24"></Icon>
|
||||
<span class="title">{{ label }}</span>
|
||||
<slot name="actions"></slot>
|
||||
</n-flex>
|
||||
<Codemirror
|
||||
v-model="code"
|
||||
indentWithTab
|
||||
:extensions="[styleTheme, lang, isDark ? oneDark : smoothy]"
|
||||
:extensions="[styleTheme, lang, highlightField, isDark ? oneDark : smoothy]"
|
||||
:disabled="props.readonly"
|
||||
:tabSize="4"
|
||||
:placeholder="props.placeholder"
|
||||
:style="{
|
||||
height: !!props.label ? 'calc(100% - 60px)' : '100%',
|
||||
height: 'calc(100% - 60px)',
|
||||
fontSize: props.fontSize + 'px',
|
||||
}"
|
||||
@change="onChange"
|
||||
@ready="onReady"
|
||||
/>
|
||||
</template>
|
||||
<style scoped>
|
||||
.header {
|
||||
padding: 12px 20px;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
198
src/components/FloatingPanel.vue
Normal file
198
src/components/FloatingPanel.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue"
|
||||
import { Icon } from "@iconify/vue"
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
variables?: Record<string, any>
|
||||
output?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
variables: () => ({}),
|
||||
output: "",
|
||||
})
|
||||
|
||||
const emit = defineEmits(["close"])
|
||||
|
||||
// 格式化变量显示
|
||||
const formattedVariables = computed(() => {
|
||||
if (!props.variables || Object.keys(props.variables).length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
return Object.entries(props.variables).map(([key, value]) => {
|
||||
// 处理特殊类型
|
||||
let displayValue = ""
|
||||
let displayType = typeof value
|
||||
|
||||
if (Array.isArray(value) && value.length === 2 &&
|
||||
value[0] === "IMPORTED_FAUX_PRIMITIVE" &&
|
||||
value[1] === "imported object") {
|
||||
displayValue = ""
|
||||
displayType = "function"
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
displayValue = JSON.stringify(value, null, 2)
|
||||
} else {
|
||||
displayValue = String(value)
|
||||
}
|
||||
|
||||
return {
|
||||
name: key,
|
||||
value: displayValue,
|
||||
type: displayType,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 格式化输出显示
|
||||
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
|
||||
})
|
||||
|
||||
function closePanel() {
|
||||
emit("close")
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card v-if="visible" class="floating-panel" :bordered="true" size="small">
|
||||
<template #header>
|
||||
<n-flex justify="space-between" align="center">
|
||||
<n-flex align="center" :gap="8">
|
||||
<n-icon>
|
||||
<Icon icon="mdi:bug" :width="16" :height="16" />
|
||||
</n-icon>
|
||||
<n-text strong>调试信息</n-text>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<n-space vertical :size="16" class="panel-content">
|
||||
<!-- 变量部分 -->
|
||||
<n-collapse :default-expanded-names="['variables']">
|
||||
<n-collapse-item title="变量" name="variables">
|
||||
<template #header>
|
||||
<n-flex align="center" :gap="8">
|
||||
<n-icon>
|
||||
<Icon icon="mdi:variable" :width="14" :height="14" />
|
||||
</n-icon>
|
||||
<n-text>变量</n-text>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
<div v-if="formattedVariables.length === 0">
|
||||
<n-text
|
||||
type="info"
|
||||
class="no-variables-text"
|
||||
>
|
||||
暂无变量
|
||||
</n-text>
|
||||
</div>
|
||||
<n-space v-else vertical :size="12">
|
||||
<n-card
|
||||
v-for="variable in formattedVariables"
|
||||
:key="variable.name"
|
||||
size="small"
|
||||
:bordered="true"
|
||||
>
|
||||
<n-flex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
class="variable-header"
|
||||
>
|
||||
<n-text strong :type="'primary'">{{ variable.name }}</n-text>
|
||||
<n-tag size="small" type="info">{{ variable.type }}</n-tag>
|
||||
</n-flex>
|
||||
<n-text
|
||||
code
|
||||
class="variable-value"
|
||||
>
|
||||
{{ variable.value }}
|
||||
</n-text>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
|
||||
<!-- 输出部分 -->
|
||||
<n-collapse v-if="formattedOutput" :default-expanded-names="['output']">
|
||||
<n-collapse-item :title="`输出 (${outputLines} 行)`" name="output">
|
||||
<template #header>
|
||||
<n-flex align="center" :gap="8">
|
||||
<n-icon>
|
||||
<Icon icon="mdi:console" :width="14" :height="14" />
|
||||
</n-icon>
|
||||
<n-text>输出 ({{ outputLines }} 行)</n-text>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
<n-card size="small" :bordered="true">
|
||||
<n-text
|
||||
code
|
||||
class="output-text"
|
||||
>
|
||||
{{ formattedOutput }}
|
||||
</n-text>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.floating-panel {
|
||||
font-family: "Monaco", "Consolas", monospace;
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 120px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.output-text {
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.no-variables-text {
|
||||
font-style: italic;
|
||||
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>
|
||||
@@ -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<any>(null)
|
||||
const currentStep = ref(0)
|
||||
const showFloatingPanel = ref(false)
|
||||
const isAutoRunning = ref(false)
|
||||
const autoRunInterval = ref<number | null>(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,21 +144,119 @@ 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毫秒执行一步
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DebugEditor
|
||||
label="代码区"
|
||||
icon="streamline-emojis:lemon"
|
||||
:font-size="size"
|
||||
v-model="code.value"
|
||||
:language="code.language"
|
||||
>
|
||||
<template #actions>
|
||||
<n-flex align="center" class="header">
|
||||
<Icon icon="streamline-emojis:lemon" :width="24" :height="24"></Icon>
|
||||
<span class="title">代码区</span>
|
||||
<n-button
|
||||
quaternary
|
||||
type="error"
|
||||
@@ -36,8 +265,152 @@ async function handleDebug() {
|
||||
>
|
||||
调试
|
||||
</n-button>
|
||||
<template v-if="showDebug">
|
||||
<n-tooltip>
|
||||
<template #trigger>
|
||||
<n-button text @click="firstStep">
|
||||
<template #icon>
|
||||
<Icon icon="tabler:player-skip-back" />
|
||||
</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' : 'success'"
|
||||
text
|
||||
@click="autoRun"
|
||||
>
|
||||
<template #icon>
|
||||
<Icon
|
||||
:icon="
|
||||
isAutoRunning ? 'tabler:player-pause' : 'tabler:player-play'
|
||||
"
|
||||
/>
|
||||
</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="tabler:player-skip-forward" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
最后一步
|
||||
</n-tooltip>
|
||||
<n-tooltip>
|
||||
<template #trigger>
|
||||
<n-button text type="error" @click="closeDebug">
|
||||
<template #icon>
|
||||
<Icon icon="tabler:x" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
关闭调试
|
||||
</n-tooltip>
|
||||
</template>
|
||||
<n-button quaternary type="primary" @click="copy">复制</n-button>
|
||||
<n-button quaternary @click="reset">清空</n-button>
|
||||
</template>
|
||||
</DebugEditor>
|
||||
<n-button
|
||||
v-if="showDebug"
|
||||
quaternary
|
||||
type="info"
|
||||
@click="showFloatingPanel = !showFloatingPanel"
|
||||
>
|
||||
{{ showFloatingPanel ? "隐藏面板" : "显示面板" }}
|
||||
</n-button>
|
||||
<!-- 调试进度条 -->
|
||||
<n-flex
|
||||
align="center"
|
||||
v-if="showDebug && debugData && debugData.trace"
|
||||
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-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"
|
||||
/>
|
||||
|
||||
<!-- 浮动面板 -->
|
||||
<FloatingPanel
|
||||
: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;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.debug-progress {
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-disabled);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
14
src/main.ts
14
src/main.ts
@@ -1,6 +1,9 @@
|
||||
import { addAPIProvider } from "@iconify/vue"
|
||||
import {
|
||||
NButton,
|
||||
NCard,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NConfigProvider,
|
||||
NDropdown,
|
||||
NFlex,
|
||||
@@ -14,11 +17,15 @@ import {
|
||||
NModal,
|
||||
NPopover,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSplit,
|
||||
NTabPane,
|
||||
NTabs,
|
||||
NTag,
|
||||
NSpin,
|
||||
NText,
|
||||
NTooltip,
|
||||
NSlider,
|
||||
create,
|
||||
} from "naive-ui"
|
||||
import "normalize.css"
|
||||
@@ -28,6 +35,9 @@ import App from "./App.vue"
|
||||
const naive = create({
|
||||
components: [
|
||||
NButton,
|
||||
NCard,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NConfigProvider,
|
||||
NMessageProvider,
|
||||
NLayout,
|
||||
@@ -47,6 +57,10 @@ const naive = create({
|
||||
NTabPane,
|
||||
NDropdown,
|
||||
NSpin,
|
||||
NSpace,
|
||||
NText,
|
||||
NTooltip,
|
||||
NSlider,
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user