diff --git a/packages/cosmic-swingset/lib/ag-solo/html/index.html b/packages/cosmic-swingset/lib/ag-solo/html/index.html index 2a7823a3493..a49fab4edf1 100644 --- a/packages/cosmic-swingset/lib/ag-solo/html/index.html +++ b/packages/cosmic-swingset/lib/ag-solo/html/index.html @@ -114,12 +114,17 @@ padding-bottom: 12px; } - .history>div> :first-child { + .history>.msg-line { + background: antiquewhite; + font-style: oblique; + } + + .history>div>.command-line :first-child, .history>div>.history-line :first-child { text-align: right; color: #424248; } - .history>div> :nth-child(2) { + .history>div>.command-line :nth-child(2), .history>div>.history-line :nth-child(2) { text-align: left; padding-left: 8px; /* Overflow must be set (to auto or hidden) to force the word break to apply. diff --git a/packages/cosmic-swingset/lib/ag-solo/html/main.js b/packages/cosmic-swingset/lib/ag-solo/html/main.js index f7057ec30ad..f80128c9b45 100644 --- a/packages/cosmic-swingset/lib/ag-solo/html/main.js +++ b/packages/cosmic-swingset/lib/ag-solo/html/main.js @@ -44,27 +44,53 @@ function run() { const commands = []; - function addHistoryRow(h, histnum, kind, value) { + function linesToHTML(lines) { + return lines + .split('\n') + .map(l => + l + .replace('&', '&') + .replace('<', '<') + .replace(/\t/g, ' ') + .replace(/\s/g, ' '), + ) + .join('
'); + } + + function addHistoryRow(h, histnum, kind, value, msgs) { + if (histnum >= 0) { + const row = document.createElement('div'); + row.className = `${kind}-line`; + const label = document.createElement('div'); + label.textContent = `${kind}[${histnum}]`; + const content = document.createElement('div'); + content.id = `${kind}-${histnum}`; + content.textContent = `${value}`; + row.appendChild(label); + row.appendChild(content); + h.append(row); + } + // Write out any messages attached to this line. const row = document.createElement('div'); - row.className = `${kind}-line`; - const label = document.createElement('div'); - label.textContent = `${kind}[${histnum}]`; - const content = document.createElement('div'); - content.id = `${kind}-${histnum}`; - content.textContent = `${value}`; - row.appendChild(label); - row.appendChild(content); + row.className = 'msg-line'; + const m = document.createElement('div'); + m.id = `msg-${kind}-${histnum}`; + if (msgs) { + m.innerHTML = linesToHTML(`${msgs}`); + } + row.appendChild(document.createElement('div')); + row.appendChild(m); h.append(row); } - function addHistoryEntry(histnum, command, result) { + function addHistoryEntry(histnum, command, result, consoles) { const h = document.getElementById('history'); - addHistoryRow(h, histnum, 'command', command); - addHistoryRow(h, histnum, 'history', result); + addHistoryRow(h, histnum, 'command', command, consoles.command || ''); + addHistoryRow(h, histnum, 'history', result, consoles.display || ''); commands[histnum] = command; } - function updateHistory(histnum, command, result) { + function updateHistory(histnum, command, result, consoles = {}) { const h = document.getElementById('history'); const isScrolledToBottom = h.scrollHeight - h.clientHeight <= h.scrollTop + 1; @@ -74,10 +100,14 @@ function run() { const c = document.getElementById(`command-${histnum}`); if (c) { const h1 = document.getElementById(`history-${histnum}`); + const m1 = document.getElementById(`msg-command-${histnum}`); + const m2 = document.getElementById(`msg-history-${histnum}`); c.textContent = `${command}`; + m1.innerHTML = linesToHTML(`${consoles.command}`); h1.textContent = `${result}`; + m2.innerHTML = linesToHTML(`${consoles.display}`); } else { - addHistoryEntry(histnum, command, result); + addHistoryEntry(histnum, command, result, consoles); } if (isScrolledToBottom) { setTimeout(() => (h.scrollTop = h.scrollHeight), 0); @@ -97,7 +127,7 @@ function run() { // we receive commands to update result boxes if (obj.type === 'updateHistory') { // these args come from calls to vat-http.js updateHistorySlot() - updateHistory(obj.histnum, obj.command, obj.display); + updateHistory(obj.histnum, obj.command, obj.display, obj.consoles); } else { console.log(`unknown WS type in:`, obj); } diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/repl.js b/packages/cosmic-swingset/lib/ag-solo/vats/repl.js index a839f158676..408600ad3b3 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/repl.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/repl.js @@ -1,6 +1,7 @@ import { makeEvaluators } from '@agoric/evaluate'; import harden from '@agoric/harden'; import { isPromise } from '@agoric/produce-promise'; +import { makeConsole } from '@agoric/swingset-vat/src/makeConsole'; // A REPL-specific JSON stringify. export function stringify( @@ -30,6 +31,8 @@ export function stringify( already.add(value); let ret = ''; + const spcs = spaces === undefined ? '' : `\n${' '.repeat(spaces)}`; + const nextSpaces = spaces === undefined ? undefined : spaces + 2; if (getInterfaceOf && getInterfaceOf(value) !== undefined) { ret += `${value}`; } else if (Array.isArray(value)) { @@ -37,9 +40,17 @@ export function stringify( let sep = ''; for (let i = 0; i < value.length; i += 1) { - ret += sep + stringify(value[i], spaces, getInterfaceOf, already); + ret += `${sep}${spcs}${stringify( + value[i], + nextSpaces, + getInterfaceOf, + already, + )}`; sep = ','; } + if (sep !== '') { + ret += spcs; + } ret += ']'; return ret; } @@ -47,38 +58,86 @@ export function stringify( ret += '{'; let sep = ''; for (const key of Object.keys(value)) { - ret += `${sep}${JSON.stringify(key, undefined, spaces)}:${stringify( - value[key], - spaces, - getInterfaceOf, - already, - )}`; + ret += `${sep}${spcs}${JSON.stringify( + key, + undefined, + nextSpaces, + )}:${stringify(value[key], nextSpaces, getInterfaceOf, already)}`; sep = ','; } + if (sep !== '') { + ret += spcs; + } ret += '}'; return ret; } export function getReplHandler(E, homeObjects, send, vatPowers) { - const commands = {}; - const history = {}; - const display = {}; let highestHistory = -1; + const commands = { + [highestHistory]: '', + }; + const history = { + [highestHistory]: '', + }; + const display = { + [highestHistory]: '', + }; const replHandles = new Set(); + let consoleOffset = highestHistory * 2 + 1; + const consoleRegions = { + [consoleOffset - 1]: [], + [consoleOffset]: [], + }; + + // Create a message much like a console.log would. + function joinMsg(args) { + let ret = ''; + let sep = ''; + for (const a of args) { + let s; + if (typeof a === 'string') { + s = a; + } else { + s = stringify(a, 2, vatPowers.getInterfaceOf); + } + ret += `${sep}${s}`; + sep = ' '; + } + return ret; + } function updateHistorySlot(histnum) { - // console.debug(`sendBroadcast ${histnum}`); + console.warn(`sendBroadcast ${histnum}`, histnum, consoleOffset); send( { type: 'updateHistory', histnum, command: commands[histnum], display: display[histnum], + consoles: { + command: consoleRegions[histnum * 2].join('\n'), + display: consoleRegions[histnum * 2 + 1].join('\n'), + }, }, [...replHandles.keys()], ); } + function writeToConsole(...args) { + consoleRegions[consoleOffset].push(joinMsg(args)); + updateHistorySlot(Math.floor(consoleOffset / 2)); + } + + const replConsole = makeConsole({ + debug: writeToConsole, + log: writeToConsole, + info: writeToConsole, + warn: writeToConsole, + error: writeToConsole, + }); + + replConsole.log(`Welcome to Agoric!`); const { evaluateProgram } = makeEvaluators({ sloppyGlobals: true }); const handler = { @@ -88,7 +147,7 @@ export function getReplHandler(E, homeObjects, send, vatPowers) { rebroadcastHistory() { // console.debug(`rebroadcastHistory`, highestHistory); - for (let histnum = 0; histnum <= highestHistory; histnum += 1) { + for (let histnum = -1; histnum <= highestHistory; histnum += 1) { updateHistorySlot(histnum); } return true; @@ -102,18 +161,23 @@ export function getReplHandler(E, homeObjects, send, vatPowers) { `histnum ${histnum} is not larger than highestHistory ${highestHistory}`, ); } + highestHistory = histnum; commands[histnum] = body; // Need this concatenation to bypass direct eval test in realms-shim. // eslint-disable-next-line no-useless-concat + consoleOffset = histnum * 2; + consoleRegions[consoleOffset] = []; + consoleRegions[consoleOffset + 1] = []; + // eslint-disable-next-line no-useless-concat display[histnum] = `working on eval` + `(${body})`; updateHistorySlot(histnum); const endowments = { ...vatPowers, - console, + console: replConsole, E, commands, history, @@ -128,7 +192,10 @@ export function getReplHandler(E, homeObjects, send, vatPowers) { } catch (e) { console.log(`error in eval`, e); history[histnum] = e; - display[histnum] = `exception: ${e}`; + display[histnum] = joinMsg('exception:', e); + } finally { + // Advance to the region after the display. + consoleOffset += 1; } if (isPromise(r)) {