updates
This commit is contained in:
+1
-1
@@ -5,6 +5,6 @@ function bareStdin(ctx) {
|
|||||||
|
|
||||||
async function run(ctx, argv) {
|
async function run(ctx, argv) {
|
||||||
ctx.console.log(
|
ctx.console.log(
|
||||||
'Bare OS — builtins: cd, export, exit | /bin: basename cat clear date dirname echo env false head help hostname id ls nl pathchk printenv pwd rm seq sleep sort tail test touch true tty uname wc which whoami'
|
'Bare OS — builtins: cd, export, exit, quit | /bin: basename cat clear date dirname echo env false head help hostname id ls nl pathchk printenv pwd rm seq sleep sort tail test touch true tty uname wc which whoami'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ async function start(ctx) {
|
|||||||
const rel = await drive.get('/etc/os-release')
|
const rel = await drive.get('/etc/os-release')
|
||||||
if (rel) console.log(b4a.toString(rel))
|
if (rel) console.log(b4a.toString(rel))
|
||||||
console.log(
|
console.log(
|
||||||
'Bare operating system — POSIX-ish shell: cd, export, exit | try: help, ls /bin, pwd'
|
'Bare operating system — POSIX-ish shell: cd, export, exit, quit | try: help, ls /bin, pwd'
|
||||||
)
|
)
|
||||||
while (true) {
|
while (true) {
|
||||||
const line = await readLine('bare-os> ')
|
const line = await readLine('bare-os> ')
|
||||||
|
|||||||
@@ -5,8 +5,15 @@ import {
|
|||||||
ghostFromHistory,
|
ghostFromHistory,
|
||||||
searchHistorySubstring
|
searchHistorySubstring
|
||||||
} from './fish-history.js'
|
} from './fish-history.js'
|
||||||
|
import {
|
||||||
|
clearEntireLine,
|
||||||
|
clearScreenDown,
|
||||||
|
clearCurrentLine,
|
||||||
|
cursorTo,
|
||||||
|
moveCursor
|
||||||
|
} from './tty-ansi.js'
|
||||||
|
|
||||||
export const SHELL_BUILTINS = ['cd', 'export', 'exit']
|
export const SHELL_BUILTINS = ['cd', 'export', 'exit', 'quit']
|
||||||
|
|
||||||
/** @type {Record<string, string[]>} */
|
/** @type {Record<string, string[]>} */
|
||||||
export const FLAG_COMPLETION = {
|
export const FLAG_COMPLETION = {
|
||||||
@@ -30,8 +37,54 @@ export function fuzzyMatch(str, pattern) {
|
|||||||
return patternIdx === pattern.length
|
return patternIdx === pattern.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Visible length for cursor placement (strip CSI sequences, not only SGR `m`). */
|
||||||
function stripAnsi(str) {
|
function stripAnsi(str) {
|
||||||
return str.replace(/\x1b\[[0-9;]*m/g, '')
|
return str.replace(/\x1b\[[\d;?]*[A-Za-z]/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTY width for soft-wrap math. If the real terminal is narrower than we assume,
|
||||||
|
* we treat the line as one row and only clear one physical row — the rest of the
|
||||||
|
* prompt (e.g. `[user@…`) stays as garbage and the user sees a lone `>`.
|
||||||
|
* When width is unknown (common on Bare/Pear), use a conservative default.
|
||||||
|
* @param {import('stream').Writable & { columns?: number }} stdout
|
||||||
|
*/
|
||||||
|
function effectiveTermCols(stdout) {
|
||||||
|
const w =
|
||||||
|
Number(stdout.columns) ||
|
||||||
|
Number(globalThis.process?.stdout?.columns) ||
|
||||||
|
Number(globalThis.process?.env?.COLUMNS)
|
||||||
|
if (Number.isFinite(w) && w > 0) {
|
||||||
|
return Math.max(20, Math.min(Math.trunc(w), 512))
|
||||||
|
}
|
||||||
|
return 40
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visible column/row after `str` on a terminal (soft-wrap model; ASCII width 1).
|
||||||
|
* @returns {{ cols: number, rows: number }}
|
||||||
|
*/
|
||||||
|
function displayPos(str, termCols) {
|
||||||
|
const col = Math.max(1, termCols)
|
||||||
|
let offset = 0
|
||||||
|
let rows = 0
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const c = str[i]
|
||||||
|
if (c === '\n') {
|
||||||
|
rows += Math.ceil(offset / col) || 1
|
||||||
|
offset = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (c === '\t') {
|
||||||
|
const tabSize = 8
|
||||||
|
offset += tabSize - (offset % tabSize)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
const cols = offset % col
|
||||||
|
rows += (offset - cols) / col
|
||||||
|
return { cols, rows }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,6 +201,10 @@ export async function createFishReadLine(ctx, opts) {
|
|||||||
let historySearchResults = []
|
let historySearchResults = []
|
||||||
let historySearchIndex = -1
|
let historySearchIndex = -1
|
||||||
let savedLine = ''
|
let savedLine = ''
|
||||||
|
/** Previous cursor row (0-based) within the prompt+input block; used for wrap-aware redraw. */
|
||||||
|
let prevCursorRows = 0
|
||||||
|
/** Rows spanned by last painted prompt+input+ghost (>=1); forces full refresh when >1. */
|
||||||
|
let prevPaintRowCount = 0
|
||||||
|
|
||||||
/** @type {((v: string | null) => void) | null} */
|
/** @type {((v: string | null) => void) | null} */
|
||||||
let pendingResolve = null
|
let pendingResolve = null
|
||||||
@@ -161,47 +218,123 @@ export async function createFishReadLine(ctx, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render(showHistorySearch = false) {
|
function render(showHistorySearch = false) {
|
||||||
|
void showHistorySearch
|
||||||
|
|
||||||
if (lines.length > 1) {
|
if (lines.length > 1) {
|
||||||
|
prevCursorRows = 0
|
||||||
|
prevPaintRowCount = 0
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
if (typeof stdout.cursorTo === 'function') stdout.cursorTo(0)
|
clearCurrentLine(stdout)
|
||||||
if (typeof stdout.clearLine === 'function') stdout.clearLine(0)
|
|
||||||
if (i < lines.length - 1) stdout.write('\n')
|
if (i < lines.length - 1) stdout.write('\n')
|
||||||
}
|
}
|
||||||
if (typeof stdout.cursorTo === 'function') stdout.cursorTo(0)
|
clearCurrentLine(stdout)
|
||||||
stdout.write('\x1b[' + (lines.length - 1) + 'A')
|
stdout.write('\x1b[' + (lines.length - 1) + 'A')
|
||||||
} else {
|
|
||||||
if (typeof stdout.clearLine === 'function') stdout.clearLine(0)
|
|
||||||
if (typeof stdout.cursorTo === 'function') stdout.cursorTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const isLastLine = i === lines.length - 1
|
const isLastLine = i === lines.length - 1
|
||||||
const prompt = getPrompt(i > 0)
|
const prompt = getPrompt(i > 0)
|
||||||
const currentLine = lines[i]
|
const currentLine = lines[i]
|
||||||
const coloredLine = highlightLine(currentLine, i)
|
const coloredLine = highlightLine(currentLine, i)
|
||||||
stdout.write(prompt + coloredLine)
|
stdout.write(prompt + coloredLine)
|
||||||
if (
|
if (
|
||||||
isLastLine &&
|
isLastLine &&
|
||||||
ghost &&
|
ghost &&
|
||||||
currentLine.length > 0 &&
|
currentLine.length > 0 &&
|
||||||
ghost.startsWith(currentLine) &&
|
ghost.startsWith(currentLine) &&
|
||||||
!historySearchActive
|
!historySearchActive
|
||||||
) {
|
) {
|
||||||
stdout.write(`\x1b[90m${ghost.slice(currentLine.length)}\x1b[0m`)
|
stdout.write(`\x1b[90m${ghost.slice(currentLine.length)}\x1b[0m`)
|
||||||
|
}
|
||||||
|
if (!isLastLine) stdout.write('\n')
|
||||||
}
|
}
|
||||||
if (!isLastLine) stdout.write('\n')
|
|
||||||
|
const cursorRowPrompt = getPrompt(currentLineIndex > 0)
|
||||||
|
const visualPromptLength = stripAnsi(cursorRowPrompt).length
|
||||||
|
const col = visualPromptLength + cursor
|
||||||
|
cursorTo(stdout, col)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySearchActive) {
|
if (historySearchActive) {
|
||||||
|
prevCursorRows = 0
|
||||||
|
prevPaintRowCount = 0
|
||||||
|
clearEntireLine(stdout)
|
||||||
|
const prompt = getPrompt(false)
|
||||||
|
const currentLine = lines[0]
|
||||||
|
const coloredLine = highlightLine(currentLine, 0)
|
||||||
|
stdout.write(prompt + coloredLine)
|
||||||
|
if (
|
||||||
|
ghost &&
|
||||||
|
currentLine.length > 0 &&
|
||||||
|
ghost.startsWith(currentLine)
|
||||||
|
) {
|
||||||
|
stdout.write(`\x1b[90m${ghost.slice(currentLine.length)}\x1b[0m`)
|
||||||
|
}
|
||||||
stdout.write(
|
stdout.write(
|
||||||
`\n\x1b[36m(reverse-i-search)'${historySearchQuery}':\x1b[0m ${historySearchResults[historySearchIndex] || ''}`
|
`\n\x1b[36m(reverse-i-search)'${historySearchQuery}':\x1b[0m ${historySearchResults[historySearchIndex] || ''}`
|
||||||
)
|
)
|
||||||
|
const vp = stripAnsi(prompt).length
|
||||||
|
cursorTo(stdout, vp + cursor)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastPrompt = getPrompt(lines.length > 1)
|
const termCols = effectiveTermCols(stdout)
|
||||||
const visualPromptLength = stripAnsi(lastPrompt).length
|
const prompt = getPrompt(false)
|
||||||
if (typeof stdout.cursorTo === 'function') {
|
const currentLine = lines[0]
|
||||||
stdout.cursorTo(visualPromptLength + cursor)
|
const coloredLine = highlightLine(currentLine, 0)
|
||||||
|
const showGhost = Boolean(
|
||||||
|
ghost &&
|
||||||
|
currentLine.length > 0 &&
|
||||||
|
ghost.startsWith(currentLine)
|
||||||
|
)
|
||||||
|
const ghostWrite = showGhost
|
||||||
|
? `\x1b[90m${ghost.slice(currentLine.length)}\x1b[0m`
|
||||||
|
: ''
|
||||||
|
const visPrompt = stripAnsi(prompt)
|
||||||
|
const ghostVis = showGhost ? ghost.slice(currentLine.length) : ''
|
||||||
|
const fullVis = visPrompt + currentLine + ghostVis
|
||||||
|
const strBefore = visPrompt + currentLine.slice(0, cursor)
|
||||||
|
const lineEnd = displayPos(fullVis, termCols)
|
||||||
|
const curEnd = displayPos(strBefore, termCols)
|
||||||
|
const paintRowCount = lineEnd.rows + 1
|
||||||
|
const needFullRefresh =
|
||||||
|
lineEnd.rows > 0 ||
|
||||||
|
curEnd.rows > 0 ||
|
||||||
|
prevCursorRows > 0 ||
|
||||||
|
prevPaintRowCount > 1
|
||||||
|
|
||||||
|
const out = prompt + coloredLine + ghostWrite
|
||||||
|
|
||||||
|
function moveUp(n) {
|
||||||
|
if (n <= 0) return
|
||||||
|
moveCursor(stdout, 0, -n)
|
||||||
|
}
|
||||||
|
function toCol0() {
|
||||||
|
cursorTo(stdout, 0)
|
||||||
|
}
|
||||||
|
function clrDown() {
|
||||||
|
clearScreenDown(stdout)
|
||||||
|
}
|
||||||
|
function toCol(x) {
|
||||||
|
cursorTo(stdout, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needFullRefresh) {
|
||||||
|
clearEntireLine(stdout)
|
||||||
|
stdout.write(out)
|
||||||
|
toCol(curEnd.cols)
|
||||||
|
prevCursorRows = 0
|
||||||
|
prevPaintRowCount = paintRowCount
|
||||||
|
} else {
|
||||||
|
moveUp(prevCursorRows)
|
||||||
|
toCol0()
|
||||||
|
clrDown()
|
||||||
|
stdout.write(out)
|
||||||
|
toCol(curEnd.cols)
|
||||||
|
const diff = lineEnd.rows - curEnd.rows
|
||||||
|
if (diff > 0) moveUp(diff)
|
||||||
|
prevCursorRows = curEnd.rows
|
||||||
|
prevPaintRowCount = paintRowCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,6 +429,8 @@ export async function createFishReadLine(ctx, opts) {
|
|||||||
historySearchQuery = ''
|
historySearchQuery = ''
|
||||||
historySearchResults = []
|
historySearchResults = []
|
||||||
historySearchIndex = -1
|
historySearchIndex = -1
|
||||||
|
prevCursorRows = 0
|
||||||
|
prevPaintRowCount = 0
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
if (cmd === null) resolve(null)
|
if (cmd === null) resolve(null)
|
||||||
else resolve(typeof cmd === 'string' ? cmd : '')
|
else resolve(typeof cmd === 'string' ? cmd : '')
|
||||||
@@ -323,6 +458,8 @@ export async function createFishReadLine(ctx, opts) {
|
|||||||
ghost = ''
|
ghost = ''
|
||||||
tabMatches = []
|
tabMatches = []
|
||||||
tabIndex = -1
|
tabIndex = -1
|
||||||
|
prevCursorRows = 0
|
||||||
|
prevPaintRowCount = 0
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -555,6 +692,8 @@ export async function createFishReadLine(ctx, opts) {
|
|||||||
return function fishReadLine(_prompt) {
|
return function fishReadLine(_prompt) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
pendingResolve = resolve
|
pendingResolve = resolve
|
||||||
|
prevCursorRows = 0
|
||||||
|
prevPaintRowCount = 0
|
||||||
line = ''
|
line = ''
|
||||||
lines = ['']
|
lines = ['']
|
||||||
currentLineIndex = 0
|
currentLineIndex = 0
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ export async function execShellLine(ctx, line) {
|
|||||||
const eq = a.indexOf('=')
|
const eq = a.indexOf('=')
|
||||||
if (eq > 0) env[a.slice(0, eq)] = expandWord(a.slice(eq + 1), env)
|
if (eq > 0) env[a.slice(0, eq)] = expandWord(a.slice(eq + 1), env)
|
||||||
}
|
}
|
||||||
} else if (name === 'exit') {
|
} else if (name === 'exit' || name === 'quit') {
|
||||||
code = 'exit'
|
code = 'exit'
|
||||||
} else {
|
} else {
|
||||||
const childCtx =
|
const childCtx =
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* Minimal CSI helpers for TTY output (no node:readline).
|
||||||
|
* Semantics match Node readline: x/y are 0-based; CHA/CUP use 1-based parameters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @param {unknown} n */
|
||||||
|
function clampNonNegInt(n) {
|
||||||
|
if (!Number.isFinite(n)) return 0
|
||||||
|
const i = Math.trunc(n)
|
||||||
|
return i < 0 ? 0 : i
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('stream').Writable | null | undefined} stream
|
||||||
|
* @param {number} x 0-based column
|
||||||
|
* @param {number} [y] 0-based row; if omitted, CHA only
|
||||||
|
*/
|
||||||
|
export function cursorTo(stream, x, y) {
|
||||||
|
if (stream == null || typeof stream.write !== 'function') return
|
||||||
|
const cx = clampNonNegInt(x)
|
||||||
|
if (typeof y === 'number' && Number.isFinite(y)) {
|
||||||
|
const cy = clampNonNegInt(y)
|
||||||
|
stream.write(`\x1b[${cy + 1};${cx + 1}H`)
|
||||||
|
} else {
|
||||||
|
stream.write(`\x1b[${cx + 1}G`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('stream').Writable | null | undefined} stream
|
||||||
|
* @param {number} dx
|
||||||
|
* @param {number} dy
|
||||||
|
*/
|
||||||
|
export function moveCursor(stream, dx, dy) {
|
||||||
|
if (stream == null || typeof stream.write !== 'function') return
|
||||||
|
const ddx = Number.isFinite(dx) ? Math.trunc(dx) : 0
|
||||||
|
const ddy = Number.isFinite(dy) ? Math.trunc(dy) : 0
|
||||||
|
if (ddy < 0) stream.write(`\x1b[${-ddy}A`)
|
||||||
|
else if (ddy > 0) stream.write(`\x1b[${ddy}B`)
|
||||||
|
if (ddx < 0) stream.write(`\x1b[${-ddx}D`)
|
||||||
|
else if (ddx > 0) stream.write(`\x1b[${ddx}C`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase from cursor to end of display (ED default).
|
||||||
|
* @param {import('stream').Writable | null | undefined} stream
|
||||||
|
*/
|
||||||
|
export function clearScreenDown(stream) {
|
||||||
|
if (stream == null || typeof stream.write !== 'function') return
|
||||||
|
stream.write('\x1b[J')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Carriage return + erase entire line (EL 2K).
|
||||||
|
* @param {import('stream').Writable | null | undefined} stream
|
||||||
|
*/
|
||||||
|
export function clearEntireLine(stream) {
|
||||||
|
if (stream == null || typeof stream.write !== 'function') return
|
||||||
|
stream.write('\r\x1b[2K')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column 0 then EL entire line (same effect as clearEntireLine on most terminals).
|
||||||
|
* @param {import('stream').Writable | null | undefined} stream
|
||||||
|
*/
|
||||||
|
export function clearCurrentLine(stream) {
|
||||||
|
if (stream == null || typeof stream.write !== 'function') return
|
||||||
|
cursorTo(stream, 0)
|
||||||
|
stream.write('\x1b[2K')
|
||||||
|
}
|
||||||
@@ -17,6 +17,13 @@ import {
|
|||||||
searchHistorySubstring
|
searchHistorySubstring
|
||||||
} from './lib/fish-history.js'
|
} from './lib/fish-history.js'
|
||||||
import { fuzzyMatch, SHELL_BUILTINS } from './lib/fish-readline.js'
|
import { fuzzyMatch, SHELL_BUILTINS } from './lib/fish-readline.js'
|
||||||
|
import {
|
||||||
|
clearCurrentLine,
|
||||||
|
clearEntireLine,
|
||||||
|
clearScreenDown,
|
||||||
|
cursorTo,
|
||||||
|
moveCursor
|
||||||
|
} from './lib/tty-ansi.js'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
@@ -186,6 +193,8 @@ async function run(ctx, argv) {
|
|||||||
t.is(ctx.vfs.getcwd(), '/bin')
|
t.is(ctx.vfs.getcwd(), '/bin')
|
||||||
await execShellLine(ctx, 'xy one two')
|
await execShellLine(ctx, 'xy one two')
|
||||||
t.is(got[0], 'xy one two')
|
t.is(got[0], 'xy one two')
|
||||||
|
t.is(await execShellLine(ctx, 'exit'), 'exit')
|
||||||
|
t.is(await execShellLine(ctx, 'quit'), 'exit')
|
||||||
await store.close()
|
await store.close()
|
||||||
rmSync(dir, { recursive: true, force: true })
|
rmSync(dir, { recursive: true, force: true })
|
||||||
})
|
})
|
||||||
@@ -251,3 +260,44 @@ test('fuzzyMatch and SHELL_BUILTINS', async (t) => {
|
|||||||
t.ok(fuzzyMatch('basename', 'bse'))
|
t.ok(fuzzyMatch('basename', 'bse'))
|
||||||
t.ok(SHELL_BUILTINS.includes('cd'))
|
t.ok(SHELL_BUILTINS.includes('cd'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('tty-ansi emits CSI for cursor and clear', async (t) => {
|
||||||
|
/** @type {string[]} */
|
||||||
|
const chunks = []
|
||||||
|
const stream = {
|
||||||
|
write(s) {
|
||||||
|
chunks.push(typeof s === 'string' ? s : String(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorTo(stream, 0)
|
||||||
|
t.is(chunks.join(''), '\x1b[1G')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
cursorTo(stream, 4)
|
||||||
|
t.is(chunks.join(''), '\x1b[5G')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
cursorTo(stream, 2, 1)
|
||||||
|
t.is(chunks.join(''), '\x1b[2;3H')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
moveCursor(stream, 0, -2)
|
||||||
|
t.is(chunks.join(''), '\x1b[2A')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
moveCursor(stream, 3, 1)
|
||||||
|
t.is(chunks.join(''), '\x1b[1B\x1b[3C')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
clearScreenDown(stream)
|
||||||
|
t.is(chunks.join(''), '\x1b[J')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
clearEntireLine(stream)
|
||||||
|
t.is(chunks.join(''), '\r\x1b[2K')
|
||||||
|
chunks.length = 0
|
||||||
|
|
||||||
|
clearCurrentLine(stream)
|
||||||
|
t.is(chunks.join(''), '\x1b[1G\x1b[2K')
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
async function run(ctx, argv) {
|
async function run(ctx, argv) {
|
||||||
ctx.console.log(
|
ctx.console.log(
|
||||||
'Bare OS — builtins: cd, export, exit | /bin: basename cat clear date dirname echo env false head help hostname id ls nl pathchk printenv pwd rm seq sleep sort tail test touch true tty uname wc which whoami'
|
'Bare OS — builtins: cd, export, exit, quit | /bin: basename cat clear date dirname echo env false head help hostname id ls nl pathchk printenv pwd rm seq sleep sort tail test touch true tty uname wc which whoami'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ function bareStdin(ctx) {
|
|||||||
|
|
||||||
async function run(ctx, argv) {
|
async function run(ctx, argv) {
|
||||||
ctx.console.log(
|
ctx.console.log(
|
||||||
'Bare OS — builtins: cd, export, exit | /bin: basename cat clear date dirname echo env false head help hostname id ls nl pathchk printenv pwd rm seq sleep sort tail test touch true tty uname wc which whoami'
|
'Bare OS — builtins: cd, export, exit, quit | /bin: basename cat clear date dirname echo env false head help hostname id ls nl pathchk printenv pwd rm seq sleep sort tail test touch true tty uname wc which whoami'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ async function start(ctx) {
|
|||||||
const rel = await drive.get('/etc/os-release')
|
const rel = await drive.get('/etc/os-release')
|
||||||
if (rel) console.log(b4a.toString(rel))
|
if (rel) console.log(b4a.toString(rel))
|
||||||
console.log(
|
console.log(
|
||||||
'Bare operating system — POSIX-ish shell: cd, export, exit | try: help, ls /bin, pwd'
|
'Bare operating system — POSIX-ish shell: cd, export, exit, quit | try: help, ls /bin, pwd'
|
||||||
)
|
)
|
||||||
while (true) {
|
while (true) {
|
||||||
const line = await readLine('bare-os> ')
|
const line = await readLine('bare-os> ')
|
||||||
|
|||||||
Reference in New Issue
Block a user