import { type Ref, onMounted, ref } from 'vue' import { http } from '../utils/http.js' import { sleep } from '../utils/sleep.js' import { rustExecute } from './rustRepl.js' const ignoredNodes = ['.diff.remove', '.vp-copy-ignore'] const RE_LANGUAGE = /language-([\w]+)/ const api = { go: 'https://api.pengzhanbo.cn/repl/golang/run', kotlin: 'https://api.pengzhanbo.cn/repl/kotlin/run', } type Lang = 'kotlin' | 'go' | 'rust' type ExecuteFn = (code: string) => Promise type ExecuteMap = Record const langAlias: Record = { kt: 'kotlin', kotlin: 'kotlin', go: 'go', rust: 'rust', rs: 'rust', } const supportLang: Lang[] = ['kotlin', 'go', 'rust'] function resolveLang(lang?: string) { return lang ? langAlias[lang] || lang : '' } export function resolveCode(el: HTMLElement): string { const clone = el.cloneNode(true) as HTMLElement clone .querySelectorAll(ignoredNodes.join(',')) .forEach(node => node.remove()) return clone.textContent || '' } export function resolveCodeInfo(el: HTMLDivElement) { const wrapper = el.querySelector('div[class*=language-]') const lang = wrapper?.className.match(RE_LANGUAGE)?.[1] const codeEl = wrapper?.querySelector('pre') as HTMLElement let code = '' if (codeEl) code = resolveCode(codeEl) return { lang: resolveLang(lang) as Lang, code } } export function useCodeRepl(el: Ref) { const lang = ref() const loaded = ref(true) const firstRun = ref(true) const finished = ref(true) const stdout = ref([]) // like print const stderr = ref([]) // like print error const error = ref('') // execute error const backendVersion = ref('') onMounted(() => { if (el.value) { const info = resolveCodeInfo(el.value) lang.value = info.lang } }) const executeMap: ExecuteMap = { kotlin: executeKotlin, go: executeGolang, rust: executeRust, } function onCleanRun() { loaded.value = false finished.value = false stdout.value = [] stderr.value = [] error.value = '' firstRun.value = true backendVersion.value = '' } async function onRunCode() { if (!el.value || !loaded.value) return const info = resolveCodeInfo(el.value) lang.value = info.lang if (!lang.value || !info.code || !supportLang.includes(lang.value)) return if (firstRun.value) firstRun.value = false loaded.value = false finished.value = false stdout.value = [] stderr.value = [] error.value = '' await executeMap[lang.value]?.(info.code) } async function executeGolang(code: string) { const res = await http.post(api.go, { code }) backendVersion.value = `v${res.version}` loaded.value = true if (res.error) { error.value = res.error finished.value = true return } const events = res.events || [] for (const event of events) { if (event.kind === 'stdout') { if (event.delay) await sleep(event.delay / 1000000) stdout.value.push(event.message) } else if (event.kind === 'stderr') { stderr.value.push(event.message) } } finished.value = true } async function executeKotlin(code: string) { const filename = 'File.kt' const res = await http.post(api.kotlin, { args: '', files: [{ name: filename, publicId: '', text: code }], }) backendVersion.value = `v${res.version}` loaded.value = true if (res.errors) { const errors = Array.isArray(res.errors[filename]) ? res.errors[filename] : [res.errors[filename]] if (errors.length) { errors.forEach( ({ message, severity }) => severity === 'ERROR' && stderr.value.push(message), ) } } stdout.value.push(res.text) finished.value = true } async function executeRust(code: string) { await rustExecute(code, { onBegin: () => { loaded.value = true finished.value = false stdout.value = [] stderr.value = [] error.value = '' backendVersion.value = 'release' }, onError(message) { error.value = message }, onStdout(message) { stdout.value.push(message) }, onStderr(message) { stderr.value.push(message) }, onEnd: () => { finished.value = true }, }) } return { onRunCode, onCleanRun, lang, backendVersion, firstRun, stderr, stdout, loaded, finished, error, } } interface GolangRequest { code: string version?: '' | 'goprev' | 'gotip' } interface GolangResponse { events?: { message: '' kind: 'stdout' | 'stderr' delay: number }[] error?: string version: string } interface KotlinRequest { args?: string files: { name: string publicId: string text: string }[] } interface KotlinResponse { text: string version: string errors: { [filename: string]: { message: string severity: 'ERROR' | 'WARNING' }[] } }