mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
feat: Add support for stop button in code runner
This commit is contained in:
+27
-6
@@ -17,17 +17,38 @@ function execFile (file: string, args: string[], options?: cp.ExecFileOptions) {
|
||||
})
|
||||
|
||||
process.on('spawn', () => {
|
||||
const output = [process.stdout, process.stderr].filter(Boolean) as NodeJS.ReadableStream[]
|
||||
resolve(mergeStreams(output))
|
||||
const output = mergeStreams([process.stdout, process.stderr].filter(Boolean) as NodeJS.ReadableStream[])
|
||||
|
||||
output.on('close', () => {
|
||||
console.log('execFile process closed')
|
||||
process.kill()
|
||||
})
|
||||
|
||||
resolve(output)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function execCmd (cmd: string, options?: cp.ExecOptions, onComplete?: () => void) {
|
||||
const process = cp.exec(cmd, { timeout: 300 * 1000, ...options })
|
||||
onComplete && process.on('close', onComplete)
|
||||
const output = [process.stdout, process.stderr].filter(Boolean) as NodeJS.ReadableStream[]
|
||||
return mergeStreams(output)
|
||||
return new Promise<string | NodeJS.ReadableStream>((resolve) => {
|
||||
const process = cp.exec(cmd, { timeout: 300 * 1000, ...options })
|
||||
|
||||
process.on('error', error => {
|
||||
resolve(error.message)
|
||||
})
|
||||
|
||||
process.on('spawn', () => {
|
||||
const output = mergeStreams([process.stdout, process.stderr].filter(Boolean) as NodeJS.ReadableStream[])
|
||||
|
||||
output.on('close', () => {
|
||||
console.log('execCmd process closed')
|
||||
onComplete && onComplete()
|
||||
process.kill()
|
||||
})
|
||||
|
||||
resolve(output)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const runCode = async (cmd: { cmd: string, args: string[] } | string, code: string): Promise<string | NodeJS.ReadableStream> => {
|
||||
|
||||
@@ -111,6 +111,7 @@ export default {
|
||||
ctx.runner.registerRunner({
|
||||
name: 'javascript',
|
||||
order: 100,
|
||||
nonInterruptible: true,
|
||||
match (language) {
|
||||
return ['js', 'javascript'].includes(language.toLowerCase())
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, defineComponent, getCurrentInstance, h, onBeforeUnmount, ref, VNode, watch, watchEffect } from 'vue'
|
||||
import { computed, defineComponent, getCurrentInstance, h, onBeforeUnmount, ref, shallowRef, VNode, watch, watchEffect } from 'vue'
|
||||
import Markdown from 'markdown-it'
|
||||
import { escape } from 'lodash-es'
|
||||
import { Plugin } from '@fe/context'
|
||||
@@ -30,8 +30,10 @@ const RunCode = defineComponent({
|
||||
const { t } = useI18n()
|
||||
const instance = getCurrentInstance()
|
||||
const result = ref('')
|
||||
const abortController = shallowRef<AbortController>()
|
||||
const hash = computed(() => md5(props.language + props.code))
|
||||
const runner = ref<CodeRunner>()
|
||||
const status = computed(() => runner.value?.nonInterruptible ? 'idle' : (abortController.value ? 'running' : 'idle'))
|
||||
const getTerminalCmd = computed(() => runner.value?.getTerminalCmd(props.language!, props.firstLine!))
|
||||
|
||||
let hasResult = false
|
||||
@@ -51,7 +53,22 @@ const RunCode = defineComponent({
|
||||
cache[hash.value] = result.value
|
||||
}
|
||||
|
||||
const abort = () => {
|
||||
if (abortController.value && !abortController.value.signal.aborted) {
|
||||
abortController.value.abort()
|
||||
abortController.value = undefined
|
||||
if (!hasResult) {
|
||||
result.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
if (status.value === 'running') {
|
||||
abort()
|
||||
return
|
||||
}
|
||||
|
||||
const { code, language } = props
|
||||
|
||||
hasResult = false
|
||||
@@ -64,7 +81,11 @@ const RunCode = defineComponent({
|
||||
result.value = t('code-run.running')
|
||||
|
||||
try {
|
||||
const { type, value: val } = await runner.value.run(language!, code)
|
||||
if (!runner.value.nonInterruptible) {
|
||||
abortController.value = new AbortController()
|
||||
}
|
||||
|
||||
const { type, value: val } = await runner.value.run(language!, code, { signal: abortController.value?.signal })
|
||||
|
||||
if (typeof val === 'string') {
|
||||
appendLog?.(type, val)
|
||||
@@ -73,6 +94,10 @@ const RunCode = defineComponent({
|
||||
|
||||
// read stream
|
||||
while (true) {
|
||||
if (abortController.value && abortController.value.signal.aborted) {
|
||||
break
|
||||
}
|
||||
|
||||
const { done, value } = await val.read()
|
||||
if (done) {
|
||||
logger.debug('run code done >', value)
|
||||
@@ -99,7 +124,11 @@ const RunCode = defineComponent({
|
||||
appendLog(type, valStr)
|
||||
}
|
||||
} catch (error: any) {
|
||||
result.value = error.message
|
||||
if (error.name !== 'AbortError') {
|
||||
appendLog?.('plain', error.message)
|
||||
}
|
||||
} finally {
|
||||
abort()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +175,8 @@ const RunCode = defineComponent({
|
||||
return [
|
||||
h('div', { class: 'p-mcr-run-code-action skip-export' }, [
|
||||
h('div', {
|
||||
title: t('code-run.run'),
|
||||
class: 'p-mcr-run-btn',
|
||||
title: status.value !== 'running' ? t('code-run.run') : t('code-run.stop'),
|
||||
class: 'p-mcr-run-btn' + (status.value === 'running' ? ' p-mcr-run-btn-stop' : ''),
|
||||
onClick: run
|
||||
}),
|
||||
h('div', {
|
||||
@@ -220,6 +249,18 @@ export default {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.markdown-view .markdown-body .p-mcr-run-btn.p-mcr-run-btn-stop {
|
||||
border: .56em #b7b3b3 solid;
|
||||
border-radius: 4px;
|
||||
animation: p-mcr-run-btn-stop 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes p-mcr-run-btn-stop {
|
||||
0% { border-color: #b7b3b3; }
|
||||
50% { border-color: #b7c3f3; }
|
||||
100% { border-color: #b7b3b3; }
|
||||
}
|
||||
|
||||
.markdown-view .markdown-body .p-mcr-run-xterm-btn {
|
||||
position: absolute;
|
||||
top: -.5em;
|
||||
|
||||
@@ -481,18 +481,22 @@ export async function convertFile (
|
||||
* Run code.
|
||||
* @param cmd
|
||||
* @param code
|
||||
* @param outputStream
|
||||
* @param opts
|
||||
* @returns result
|
||||
*/
|
||||
export async function runCode (cmd: string | { cmd: string, args: string[] }, code: string, outputStream: true): Promise<ReadableStreamDefaultReader>
|
||||
export async function runCode (cmd: string | { cmd: string, args: string[] }, code: string, outputStream?: false): Promise<string>
|
||||
export async function runCode (cmd: string | { cmd: string, args: string[] }, code: string, outputStream = false): Promise<ReadableStreamDefaultReader | string> {
|
||||
export async function runCode (cmd: string | { cmd: string, args: string[] }, code: string, opts?: { stream?: false, signal?: AbortSignal }): Promise<ReadableStreamDefaultReader>
|
||||
export async function runCode (cmd: string | { cmd: string, args: string[] }, code: string, opts?: { stream?: false, signal?: AbortSignal }): Promise<string>
|
||||
export async function runCode (cmd: string | { cmd: string, args: string[] }, code: string, opts?: { stream?: false, signal?: AbortSignal }): Promise<ReadableStreamDefaultReader | string> {
|
||||
const response = await fetchHttp('/api/run', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cmd, code })
|
||||
body: JSON.stringify({ cmd, code }),
|
||||
signal: opts?.signal
|
||||
})
|
||||
|
||||
// compatible with old version
|
||||
const outputStream = typeof opts === 'boolean' ? opts : opts?.stream
|
||||
|
||||
if (outputStream) {
|
||||
return response.body.getReader()
|
||||
} else {
|
||||
|
||||
@@ -516,12 +516,13 @@ export type Renderer = {
|
||||
export interface CodeRunner {
|
||||
name: string;
|
||||
order?: number;
|
||||
nonInterruptible?: boolean
|
||||
match: (language: string, magicComment: string) => boolean;
|
||||
getTerminalCmd: (language: string, magicComment: string) => {
|
||||
start: string,
|
||||
exit: string,
|
||||
} | null;
|
||||
run: (language: string, code: string) => Promise<{
|
||||
run: (language: string, code: string, opts?: { signal?: AbortSignal }) => Promise<{
|
||||
type: 'html' | 'plain',
|
||||
value: ReadableStreamDefaultReader | string,
|
||||
}>;
|
||||
|
||||
@@ -493,6 +493,7 @@ const data = {
|
||||
},
|
||||
'code-run': {
|
||||
'run': 'Run',
|
||||
'stop': 'Stop',
|
||||
'run-in-xterm-tips': 'Run code in terminal, %s + click do not exit',
|
||||
'run-in-xterm': 'Run in terminal',
|
||||
'running': 'Running...',
|
||||
|
||||
@@ -494,6 +494,7 @@ const data: BaseLanguage = {
|
||||
},
|
||||
'code-run': {
|
||||
'run': '运行',
|
||||
'stop': '停止',
|
||||
'run-in-xterm-tips': '在终端中运行代码,%s + 单击不退出解释器',
|
||||
'run-in-xterm': '终端中运行',
|
||||
'running': '运行中……',
|
||||
|
||||
@@ -494,6 +494,7 @@ const data: BaseLanguage = {
|
||||
},
|
||||
'code-run': {
|
||||
'run': '執行',
|
||||
'stop': '停止',
|
||||
'run-in-xterm-tips': '在終端中執行程式碼,%s + 點擊不退出解釋器',
|
||||
'run-in-xterm': '終端中執行',
|
||||
'running': '執行中……',
|
||||
|
||||
Reference in New Issue
Block a user