Files
bare-operating-system/packages/bare-os-coreutils/lib/baretop-compose.js
T
2026-04-05 17:38:46 -04:00

162 lines
4.6 KiB
JavaScript

/** Frame lines, incremental patch helpers, smoke compose for tests (preamble; no import). */
function bareTopSafeWrite(ctx, stdout, s) {
if (!stdout || typeof stdout.write !== 'function') return
try {
stdout.write(s)
} catch {
try {
ctx.console?.error?.('baretop: stdout write failed')
} catch {
/* ignore */
}
}
}
/**
* FNV-1a 32-bit for line dirty detection (item 4).
* @param {string} s
*/
function bareTopLineHash32(s) {
const str = String(s)
let h = 0x811c9dc5
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i)
h = Math.imul(h, 0x01000193)
}
return h >>> 0
}
/**
* @param {string} out
* @returns {string[]}
*/
function bareTopFrameToLines(out) {
return out.split(/\r\n/)
}
/**
* Join lines to frame string (no trailing requirement).
* @param {string[]} lines
*/
function bareTopLinesToFrame(lines) {
return lines.join('\r\n')
}
/**
* Build incremental ANSI patch (CUP + EL + line per change).
* @param {string[] | null} prevLines
* @param {string[]} nextLines
* @param {(row: number, col: number) => string} cup
* @param {{ useLineHash?: boolean, lineHashMinLen?: number }} opts
* @returns {{ patch: string, nch: number }}
*/
function bareTopBuildLinePatch(prevLines, nextLines, cup, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const useHash = o.useLineHash === true
const minLen = Number(o.lineHashMinLen) > 0 ? Number(o.lineHashMinLen) : 200
let patch = '\x1b[?25l'
let nch = 0
for (let i = 0; i < nextLines.length; i++) {
const prev = prevLines && i < prevLines.length ? prevLines[i] : undefined
const next = nextLines[i]
let changed = false
if (prev === undefined) changed = true
else if (useHash && next.length >= minLen) {
changed = bareTopLineHash32(prev) !== bareTopLineHash32(next)
} else changed = prev !== next
if (changed) {
nch++
const r = Math.max(1, Math.min(i + 1, 9999))
patch += cup(r, 1) + '\x1b[K' + next + '\r\n'
}
}
patch += '\x1b[?25h'
return { patch, nch }
}
/**
* Emit full frame or patch (item 1/10): single write.
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable} stdout
* @param {string} fullOut
* @param {{
* prevLines: string[] | null,
* rows: number,
* cols: number,
* incrementalFrameDiff: boolean,
* useLineHash: boolean,
* fullscreenPanel: boolean,
* cup: (r: number, c: number) => string
* }} o
* @returns {{ nextLines: string[], wrotePatch: boolean, patchLen: number }}
*/
function bareTopEmitFrame(ctx, stdout, fullOut, o) {
const nextLines = bareTopFrameToLines(fullOut)
let wrotePatch = false
let patchLen = 0
if (
o.incrementalFrameDiff &&
o.prevLines &&
o.prevLines.length === nextLines.length &&
nextLines.length > 2 &&
!o.fullscreenPanel
) {
const { patch, nch } = bareTopBuildLinePatch(
o.prevLines,
nextLines,
o.cup,
{ useLineHash: o.useLineHash, lineHashMinLen: 200 }
)
if (nch > 0 && nch < nextLines.length * 0.92) {
patchLen = patch.length
bareTopSafeWrite(/** @type {Record<string, unknown>} */ (ctx), stdout, patch)
wrotePatch = true
return { nextLines, wrotePatch, patchLen }
}
}
bareTopSafeWrite(/** @type {Record<string, unknown>} */ (ctx), stdout, fullOut)
return { nextLines, wrotePatch, patchLen }
}
/**
* Minimal deterministic compose for unit tests (item 96) — no TTY, stable output.
* Uses bareTopProcessRowsFromTable from preamble (baretop-ui-helpers.js before this file in bundle order).
* @param {Record<string, unknown> | null} snap
* @param {number} cols
*/
function bareTopComposeSmokeTestFrame(snap, cols) {
const c = Math.max(40, cols | 0)
const lines = []
lines.push('baretop-compose-smoke cols=' + c)
const pt =
snap &&
snap.extra &&
snap.extra.processTable &&
typeof snap.extra.processTable === 'object'
? snap.extra.processTable
: snap &&
snap.metricsLive &&
snap.metricsLive.processTable &&
typeof snap.metricsLive.processTable === 'object'
? snap.metricsLive.processTable
: null
const rows =
typeof bareTopProcessRowsFromTable === 'function'
? bareTopProcessRowsFromTable(pt)
: []
lines.push('process_rows=' + rows.length)
if (rows[0] && typeof rows[0] === 'object') {
const r = /** @type {Record<string, unknown>} */ (rows[0])
lines.push('head_pid=' + String(r.pid ?? ''))
}
return {
lines,
meta: {
layoutVersion: 'baretop-compose-1',
footerRow: lines.length + 1,
procBodyStart: null
}
}
}