import { EditorView } from '@codemirror/view' import { asciiEscapeToHtml, assertNever, log, toElement } from '#utils/utils' import { Signal } from '#utils/signal' import { getContent } from '#editor/plugins/persistence' import type { HtmlEscapedString } from 'hono/utils/html' import { connectToNose, noseSignals } from '#editor/noseClient' import type { Value } from 'reefvm' import { Compartment } from '@codemirror/state' import { lineNumbers } from '@codemirror/view' import { shrimpSetup } from '#editor/plugins/shrimpSetup' import '#editor/editor.css' const lineNumbersCompartment = new Compartment() connectToNose() export const outputSignal = new Signal() export const errorSignal = new Signal() export const multilineModeSignal = new Signal() export const Editor = () => { return ( <>
{ if (ref?.querySelector('.cm-editor')) return const view = new EditorView({ parent: ref, doc: getContent(), extensions: shrimpSetup(lineNumbersCompartment), }) multilineModeSignal.connect((isMultiline) => { view.dispatch({ effects: lineNumbersCompartment.reconfigure(isMultiline ? lineNumbers() : []), }) }) requestAnimationFrame(() => view.focus()) }} />
) } noseSignals.connect((message) => { if (message.type === 'error') { log.error(`Nose error: ${message.data}`) errorSignal.emit(`Nose error: ${message.data}`) } else if (message.type === 'reef-output') { const x = outputSignal.emit(message.data) } else if (message.type === 'connected') { outputSignal.emit(`╞ Connected to Nose VM`) } }) outputSignal.connect((value) => { const el = document.querySelector('#output')! el.innerHTML = '' el.innerHTML = asciiEscapeToHtml(valueToString(value)) }) errorSignal.connect((error) => { const el = document.querySelector('#output')! el.innerHTML = '' el.classList.add('error') el.innerHTML = asciiEscapeToHtml(error) }) type StatusBarMessage = { side: 'left' | 'right' message: string | Promise className: string order?: number } export const statusBarSignal = new Signal() statusBarSignal.connect(async ({ side, message, className, order }) => { document.querySelector(`#status-bar .${className}`)?.remove() const sideEl = document.querySelector(`#status-bar .${side}`)! const messageEl = (
{await message}
) // Now go through the nodes and put it in the right spot based on order. Higher number means further right const nodes = Array.from(sideEl.childNodes) const index = nodes.findIndex((node) => { if (!(node instanceof HTMLElement)) return false return Number(node.dataset.order) > (order ?? 0) }) if (index === -1) { sideEl.appendChild(toElement(messageEl)) } else { sideEl.insertBefore(toElement(messageEl), nodes[index]!) } }) const valueToString = (value: Value | string): string => { if (typeof value === 'string') { return value } switch (value.type) { case 'null': return 'null' case 'boolean': return value.value ? 'true' : 'false' case 'number': return value.value.toString() case 'string': return value.value case 'array': return `${value.value.map(valueToString).join('\n')}` case 'dict': { const entries = Array.from(value.value.entries()).map( ([key, val]) => `"${key}": ${valueToString(val)}` ) return `{${entries.join(', ')}}` } case 'regex': return `/${value.value.source}/` case 'function': return `` case 'native': return `` default: assertNever(value) return `` } }