/** Full-screen TUI for /bin/edit (TEA via ctx.tui; pre-SDK loop in Legacy). */ /** * @param {Record} ctx */ function bareEditTuiRunOpts(ctx) { const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} /** @type {{ altScreen?: boolean, bracketedPaste?: boolean, buffer: 'cell' }} */ const opts = { buffer: 'cell' } if ( (env.BARE_EDIT_NO_ALTSCREEN != null && String(env.BARE_EDIT_NO_ALTSCREEN) !== '') || (env.BARE_OS_TUI_NO_ALTSCREEN != null && String(env.BARE_OS_TUI_NO_ALTSCREEN) !== '') ) { opts.altScreen = false } if ( env.BARE_EDIT_NO_BRACKETED_PASTE != null && String(env.BARE_EDIT_NO_BRACKETED_PASTE) !== '' ) { opts.bracketedPaste = false } return opts } /** * @param {Record} ctx */ function bareEditMaxPromptLen(ctx) { const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} let maxPromptLen = 4096 const maxPromptRaw = env.BARE_EDIT_MAX_PROMPT if (maxPromptRaw != null && String(maxPromptRaw) !== '') { const n = parseInt(String(maxPromptRaw), 10) if (Number.isFinite(n) && n > 0) maxPromptLen = Math.min(n, 65536) } return maxPromptLen } /** * @param {string} text * @param {'saveas'|'search'|'goto'} kind * @param {number} maxPromptLen */ function bareEditPromptLineFromPaste(text, kind, maxPromptLen) { let line = String(text).split(/\r?\n/)[0] ?? '' line = line.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '') if (line.length > maxPromptLen) line = line.slice(0, maxPromptLen) if (kind === 'goto') { const m = /^(\s*\d+)/.exec(line) return m ? m[1].trim() : '' } return line } /** * Ctrl+_ (ASCII US, 0x1f) — Go to line. Decoder names it as ctrl+\x7f. * @param {unknown} msg */ function bareEditIsGotoChord(msg) { if (!msg || /** @type {{ type?: string }} */ (msg).type !== 'key') return false const m = /** @type {{ ctrl?: boolean, sequence?: string, name?: string }} */ (msg) if (!m.ctrl) return false return m.sequence === '\x1f' || m.name === '\x7f' } /** * TEA model for /bin/edit. Cell buffer so help / prompts overlay without reflow. * @param {Record} ctx * @param {{ path: string, initialText: string, argv0?: string }} opts */ function bareEditCreateTuiApp(ctx, opts) { const tui = ctx.tui const vfs = ctx.vfs const b4a = ctx.b4a const size0 = tui && typeof tui.size === 'function' ? tui.size() : {} const useColor = typeof bareEditUseColor === 'function' ? bareEditUseColor(ctx) : true return { path: opts.path || 'Untitled', display: opts.argv0 || 'edit', lang: bareEditDetectLang(opts.path || ''), buf: bareEditCreateBuffer(opts.initialText), scrollRow: 0, scrollCol: 0, savePath: opts.path || 'Untitled', mode: /** @type {'edit'|'quit_confirm'|'help'|'prompt_search'|'prompt_goto'|'prompt_saveas'} */ ( 'edit' ), promptBuf: '', promptTitle: '', lastSearch: '', maxPromptLen: bareEditMaxPromptLen(ctx), pendingQuit: false, statusMsg: '', width: size0.width || 80, height: size0.height || 24, init: function () { return null }, _contentH: function () { return Math.max(1, (this.height || 24) - 2) }, _ensureScroll: function () { const cols = Math.max(40, this.width || 80) const contentH = this._contentH() const buf = this.buf if (buf.row < this.scrollRow) this.scrollRow = buf.row if (buf.row >= this.scrollRow + contentH) this.scrollRow = buf.row - contentH + 1 if (this.scrollRow < 0) this.scrollRow = 0 if (this.scrollCol > buf.col) this.scrollCol = buf.col if (buf.col - this.scrollCol >= cols) this.scrollCol = buf.col - cols + 1 if (this.scrollCol < 0) this.scrollCol = 0 }, _save: function (targetPath) { const self = this return function () { return Promise.resolve() .then(function () { const text = bareEditJoinAll(self.buf) return vfs.writeFile(targetPath, b4a.from(text)) }) .then(function () { return { type: 'edit.saved', ok: true, path: targetPath } }) .catch(function (error) { return { type: 'edit.saved', ok: false, error: error } }) } }, _applySearch: function () { this.lastSearch = this.promptBuf if (!this.lastSearch) return const hit = bareEditFindNext( this.buf, this.lastSearch, this.buf.row, this.buf.col + 1 ) if (!hit) { const hit2 = bareEditFindNext(this.buf, this.lastSearch, 0, 0) if (hit2) { this.buf.row = hit2.row this.buf.col = hit2.col } } else { this.buf.row = hit.row this.buf.col = hit.col } }, _promptKind: function () { if (this.mode === 'prompt_search') return 'search' if (this.mode === 'prompt_goto') return 'goto' if (this.mode === 'prompt_saveas') return 'saveas' return '' }, update: function (msg) { if (msg && msg.type === 'resize') { this.width = msg.width || this.width this.height = msg.height || this.height this._ensureScroll() return [this, null] } if (msg && msg.type === 'edit.saved') { if (msg.ok) { this.buf.dirty = false this.savePath = String(msg.path || this.savePath) this.statusMsg = '' if (this.pendingQuit) return [this, tui.quit] } else { const err = msg.error this.statusMsg = 'edit: ' + (err && err.message ? err.message : String(err || 'save failed')) this.pendingQuit = false } return [this, null] } if (this.mode === 'help') { if (msg && msg.type === 'key') this.mode = 'edit' return [this, null] } if (this.mode === 'quit_confirm') { if (tui.key.matches(msg, 'ctrl+c')) { this.mode = 'edit' return [this, null] } if (tui.key.matches(msg, 'y', 'Y')) { this.pendingQuit = true this.mode = 'edit' return [this, this._save(this.savePath)] } if (tui.key.matches(msg, 'n', 'N')) return [this, tui.quit] return [this, null] } if (this._promptKind()) { if (tui.key.matches(msg, 'ctrl+c')) { this.mode = 'edit' return [this, null] } if (msg && msg.type === 'paste') { this.promptBuf = bareEditPromptLineFromPaste( String(msg.text || ''), /** @type {'saveas'|'search'|'goto'} */ (this._promptKind()), this.maxPromptLen ) return [this, null] } if (tui.key.matches(msg, 'enter')) { if (this.mode === 'prompt_search') this._applySearch() else if (this.mode === 'prompt_goto') { const n = parseInt(this.promptBuf.trim(), 10) if (Number.isFinite(n)) bareEditGotoLine(this.buf, n) } else if (this.mode === 'prompt_saveas') { const p = this.promptBuf.trim() this.mode = 'edit' if (p) { this.savePath = p return [this, this._save(this.savePath)] } } this.mode = 'edit' return [this, null] } if (tui.key.matches(msg, 'backspace')) { this.promptBuf = this.promptBuf.slice(0, -1) return [this, null] } if ( msg && msg.type === 'key' && !msg.ctrl && !msg.meta && typeof msg.sequence === 'string' && msg.sequence.length === 1 && msg.sequence >= ' ' && msg.sequence !== '\x7f' ) { if (this.promptBuf.length < this.maxPromptLen) this.promptBuf += msg.sequence } return [this, null] } if (msg && msg.type === 'paste') { bareEditInsertPasteText(this.buf, msg.text || '') this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'ctrl+x')) { if (this.buf.dirty) { this.mode = 'quit_confirm' return [this, null] } return [this, tui.quit] } if (tui.key.matches(msg, 'ctrl+s')) { return [this, this._save(this.savePath)] } if (tui.key.matches(msg, 'ctrl+o')) { this.mode = 'prompt_saveas' this.promptTitle = 'File: ' this.promptBuf = this.savePath return [this, null] } if (tui.key.matches(msg, 'ctrl+w')) { this.mode = 'prompt_search' this.promptTitle = 'Search: ' this.promptBuf = this.lastSearch return [this, null] } if (tui.key.matches(msg, 'ctrl+g')) { this.mode = 'help' return [this, null] } if (bareEditIsGotoChord(msg)) { this.mode = 'prompt_goto' this.promptTitle = 'Go to line: ' this.promptBuf = '' return [this, null] } if (tui.key.matches(msg, 'ctrl+z')) { bareEditUndo(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'ctrl+y')) { bareEditRedo(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'pageup')) { const step = Math.max(1, (this.height || 24) - 4) this.buf.row = Math.max(0, this.buf.row - step) bareEditClampCursor(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'pagedown')) { const step = Math.max(1, (this.height || 24) - 4) this.buf.row = Math.min(this.buf.lines.length - 1, this.buf.row + step) bareEditClampCursor(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'up', 'down', 'left', 'right', 'home', 'end')) { const name = msg && msg.name if ( name === 'up' || name === 'down' || name === 'left' || name === 'right' || name === 'home' || name === 'end' ) { bareEditMoveKey(this.buf, name) } this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'backspace')) { bareEditBackspace(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'delete')) { bareEditDelete(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'enter')) { bareEditNewline(this.buf) this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'tab')) { bareEditInsertChar(this.buf, ' ') this._ensureScroll() return [this, null] } if (tui.key.matches(msg, 'ctrl+c')) { return [this, null] } if ( msg && msg.type === 'key' && !msg.ctrl && !msg.meta && typeof msg.sequence === 'string' && msg.sequence.length === 1 && msg.sequence >= ' ' ) { bareEditInsertChar(this.buf, msg.sequence) this._ensureScroll() } return [this, null] }, view: function () { const cols = Math.max(40, this.width || 80) const contentH = this._contentH() this._ensureScroll() const lines = [] for (let r = 0; r < contentH; r++) { const lineIndex = this.scrollRow + r const line = this.buf.lines[lineIndex] || '' const spans = bareEditHighlightLine(line, this.lang) lines.push( bareEditPaintLineWindow(line, spans, this.scrollCol, cols, useColor) ) } const dirtyMark = this.buf.dirty ? ' [Modified]' : '' const statusRaw = this.statusMsg || this.display + ' ' + this.savePath + dirtyMark + ' ' + (this.buf.row + 1) + ',' + (this.buf.col + 1) const helpRaw = '^G Help ^O/^S Save ^X Exit ^W Find ^_ Line ^Z Undo ^Y Redo' const st = tui.style const status = st ? st() .foreground('brightwhite') .background('blue') .width(cols) .render(statusRaw) : statusRaw const help = st ? st().dim().width(cols).render(helpRaw) : helpRaw return lines.join('\n') + '\n' + status + '\n' + help }, overlays: function (size) { const cols = (size && size.width) || this.width || 80 const rows = (size && size.height) || this.height || 24 const st = tui.style /** @type {{ row: number, col: number, text: string }[]} */ const list = [] if (this.mode === 'help') { const body = 'Bare OS edit — help\n\n' + '^O ^S Save ^X Exit\n' + '^W Search ^_ Go to line\n' + '^Z Undo ^Y Redo\n' + 'Arrows move cursor; Home/End; Backspace/Del\n\n' + 'Press any key to return.' const boxed = st ? st() .border(st.borders.rounded) .padding(1, 2) .background('black') .render(body) : body const h = st ? st.height(boxed) : boxed.split('\n').length const w = st ? st.width(boxed) : 40 list.push({ row: Math.max(0, Math.floor((rows - h) / 2)), col: Math.max(0, Math.floor((cols - w) / 2)), text: boxed }) return list } if (this.mode === 'quit_confirm') { const msg = 'Save modified buffer? Y Yes N No ^C Cancel' list.push({ row: Math.max(0, rows - 2), col: 0, text: st ? st() .foreground('brightwhite') .background('blue') .width(cols) .render(msg) : msg }) return list } if (this._promptKind()) { const bar = this.promptTitle + this.promptBuf list.push({ row: Math.max(0, rows - 2), col: 0, text: st ? st() .foreground('brightwhite') .background('blue') .width(cols) .render(bar) : bar }) return list } this._ensureScroll() const cr = this.buf.row - this.scrollRow const cc = this.buf.col - this.scrollCol if (cr >= 0 && cr < rows - 2 && cc >= 0 && cc < cols) { const line = this.buf.lines[this.buf.row] || '' const ch = line.charAt(this.buf.col) || ' ' list.push({ row: cr, col: cc, text: st ? st().reverse().render(ch) : ch }) } return list } } } /** * @param {Record} ctx * @param {{ path: string, initialText: string, argv0?: string }} opts */ async function bareOsRunEditTui(ctx, opts) { if (ctx.tui && typeof ctx.tui.run === 'function') { await ctx.tui.run(bareEditCreateTuiApp(ctx, opts), bareEditTuiRunOpts(ctx)) return } await bareOsRunEditTuiLegacy(ctx, opts) } /** * Pre-SDK key loop (BARE_OS_TUI=0). * @param {Record} ctx * @param {{ path: string, initialText: string, argv0?: string }} opts */ async function bareOsRunEditTuiLegacy(ctx, opts) { const vfs = ctx.vfs const b4a = ctx.b4a const path = opts.path || 'Untitled' const display = opts.argv0 || 'edit' const lang = bareEditDetectLang(path) const buf = bareEditCreateBuffer(opts.initialText) const useColor = bareEditUseColor(ctx) const stdin = /** @type {import('stream').Readable | undefined} */ ( ctx.replStdin ) const stdout = bareEditResolveStdout(ctx) if (!stdin || !stdout) { ctx.console.error('edit: missing stdin/stdout') ctx.exitCode = 1 return } let scrollRow = 0 let scrollCol = 0 let savePath = path /** @type {'edit'|'quit_confirm'|'help'|'prompt_search'|'prompt_goto'|'prompt_saveas'} */ let mode = 'edit' let promptBuf = '' let promptTitle = '' let lastSearch = '' const envEditEarly = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} let maxPromptLen = 4096 const maxPromptRaw = envEditEarly.BARE_EDIT_MAX_PROMPT if (maxPromptRaw != null && String(maxPromptRaw) !== '') { const n = parseInt(String(maxPromptRaw), 10) if (Number.isFinite(n) && n > 0) maxPromptLen = Math.min(n, 65536) } /** * @param {string} text * @param {'saveas'|'search'|'goto'} kind */ function promptLineFromPaste(text, kind) { let line = String(text).split(/\r?\n/)[0] ?? '' line = line.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '') if (line.length > maxPromptLen) line = line.slice(0, maxPromptLen) if (kind === 'goto') { const m = /^(\s*\d+)/.exec(line) return m ? m[1].trim() : '' } return line } function termDims() { const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const cols = /** @type {{ columns?: number }} */ (stdout).columns || parseInt(env.COLUMNS || '80', 10) || 80 const rows = /** @type {{ rows?: number }} */ (stdout).rows || parseInt(env.LINES || '24', 10) || 24 return { cols: Math.max(40, cols), rows: Math.max(8, rows) } } function ensureScroll() { const { cols, rows } = termDims() const contentH = rows - 2 if (buf.row < scrollRow) scrollRow = buf.row if (buf.row >= scrollRow + contentH) scrollRow = buf.row - contentH + 1 if (scrollRow < 0) scrollRow = 0 if (scrollCol > buf.col) scrollCol = buf.col if (buf.col - scrollCol >= cols) scrollCol = buf.col - cols + 1 if (scrollCol < 0) scrollCol = 0 } function draw() { const { cols, rows } = termDims() const contentH = Math.max(1, rows - 2) ensureScroll() let out = '' out += '\x1b[?25l\x1b[2J\x1b[H' if (mode === 'help') { out += 'Bare OS edit — help\r\n\r\n' + '^O ^S Save ^X Exit\r\n' + '^W Search ^_ Go to line\r\n' + '^Z Undo ^Y Redo\r\n' + 'Arrows move cursor; Home/End; Backspace/Del\r\n\r\n' + 'Press any key to return.\r\n' out += bareEditCup(8, 1) + '\x1b[?25h' bareEditWrite(ctx, stdout, out) return } if (mode === 'quit_confirm') { for (let row = 1; row <= contentH; row++) { out += bareEditCup(row, 1) + '\x1b[K' } const msg = 'Save modified buffer? Y Yes N No ^C Cancel' const st = bareEditSgr('status', useColor) + msg + EDIT_ANSI_RESET out += bareEditCup(contentH + 1, 1) + '\x1b[K' + st out += bareEditCup(contentH + 1, 1) + '\x1b[?25h' bareEditWrite(ctx, stdout, out) return } if ( mode === 'prompt_search' || mode === 'prompt_goto' || mode === 'prompt_saveas' ) { const editorLines = Math.max(0, contentH - 1) for (let r = 0; r < editorLines; r++) { const lineIndex = scrollRow + r const line = buf.lines[lineIndex] || '' const spans = bareEditHighlightLine(line, lang) const vis = bareEditPaintLineWindow( line, spans, scrollCol, cols, useColor ) out += bareEditCup(1 + r, 1) + '\x1b[K' + vis } const barBody = bareEditSgr('status', useColor) + promptTitle + promptBuf + (useColor ? '\x1b[0m' : '') const promptRow = editorLines + 1 out += bareEditCup(promptRow, 1) + '\x1b[K' + barBody const promptCol = 1 + promptTitle.length + promptBuf.length out += bareEditCup(promptRow, promptCol) + '\x1b[?25h' bareEditWrite(ctx, stdout, out) return } for (let r = 0; r < contentH; r++) { const lineIndex = scrollRow + r const line = buf.lines[lineIndex] || '' const spans = bareEditHighlightLine(line, lang) const vis = bareEditPaintLineWindow( line, spans, scrollCol, cols, useColor ) out += bareEditCup(1 + r, 1) + '\x1b[K' + vis } const dirtyMark = buf.dirty ? ' [Modified]' : '' const statusLine = (useColor ? bareEditSgr('status', true) : '') + display + ' ' + savePath + dirtyMark + ' ' + (buf.row + 1) + ',' + (buf.col + 1) + (useColor ? EDIT_ANSI_RESET : '') const helpLine = (useColor ? bareEditSgr('dim', true) : '') + '^G Help ^O/^S Save ^X Exit ^W Find ^_ Line ^Z Undo ^Y Redo' + (useColor ? EDIT_ANSI_RESET : '') out += bareEditCup(contentH + 1, 1) + '\x1b[K' + statusLine out += bareEditCup(contentH + 2, 1) + '\x1b[K' + helpLine const curRow = 1 + (buf.row - scrollRow) const curCol = 1 + (buf.col - scrollCol) const tr = Math.min(Math.max(1, curRow), contentH) const tc = Math.min(Math.max(1, curCol), cols) out += bareEditCup(tr, tc) + '\x1b[?25h' bareEditWrite(ctx, stdout, out) } async function doSave(targetPath) { const text = bareEditJoinAll(buf) try { await vfs.writeFile(targetPath, b4a.from(text)) buf.dirty = false savePath = targetPath return true } catch (e) { ctx.console.error('edit: ' + ((e && e.message) || String(e))) return false } } const reader = bareEditCreateStdinReader(stdin) let suspended = false /** When true, alternate-screen mode was entered (?1049h); {@link bareEditWrite} must send ?1049l on exit. */ let useAltScreen = false /** When true, bracketed paste mode was enabled (?2004h); must send ?2004l on exit. */ let useBracketPaste = false try { if (typeof ctx.suspendReplForSubprocess === 'function') { ctx.suspendReplForSubprocess() suspended = true } if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true) if (typeof stdin.resume === 'function') stdin.resume() const bareEditNoAltScreen = envEditEarly.BARE_EDIT_NO_ALTSCREEN != null && String(envEditEarly.BARE_EDIT_NO_ALTSCREEN) !== '' if (!bareEditNoAltScreen) { bareEditWrite(ctx, stdout, '\x1b[?1049h') useAltScreen = true } const bareEditNoBracketPaste = envEditEarly.BARE_EDIT_NO_BRACKETED_PASTE != null && String(envEditEarly.BARE_EDIT_NO_BRACKETED_PASTE) !== '' if (!bareEditNoBracketPaste) { bareEditWrite(ctx, stdout, '\x1b[?2004h') useBracketPaste = true } draw() for (;;) { const ev = await bareEditReadKey(reader) if (ev.type === 'eof') break if (mode === 'help') { mode = 'edit' draw() continue } if (mode === 'quit_confirm') { if (ev.type === 'ctrl' && ev.code === 'interrupt') { mode = 'edit' draw() continue } if (ev.type === 'key' && ev.ch) { const u = ev.ch.toUpperCase() if (u === 'Y') { await doSave(savePath) break } if (u === 'N') break } continue } if ( mode === 'prompt_search' || mode === 'prompt_goto' || mode === 'prompt_saveas' ) { if (ev.type === 'ctrl' && ev.code === 'interrupt') { mode = 'edit' draw() continue } if (ev.type === 'paste') { if (mode === 'prompt_saveas') { promptBuf = promptLineFromPaste(ev.text || '', 'saveas') } else if (mode === 'prompt_search') { promptBuf = promptLineFromPaste(ev.text || '', 'search') } else if (mode === 'prompt_goto') { promptBuf = promptLineFromPaste(ev.text || '', 'goto') } draw() continue } if (ev.type === 'key' && ev.ch === '\n') { if (mode === 'prompt_search') { lastSearch = promptBuf if (lastSearch) { const hit = bareEditFindNext( buf, lastSearch, buf.row, buf.col + 1 ) if (!hit) { const hit2 = bareEditFindNext(buf, lastSearch, 0, 0) if (hit2) { buf.row = hit2.row buf.col = hit2.col } } else { buf.row = hit.row buf.col = hit.col } } } else if (mode === 'prompt_goto') { const n = parseInt(promptBuf.trim(), 10) if (Number.isFinite(n)) bareEditGotoLine(buf, n) } else if (mode === 'prompt_saveas') { const p = promptBuf.trim() if (p) { savePath = p await doSave(savePath) } } mode = 'edit' draw() continue } if (ev.type === 'ctrl' && ev.code === 'backspace') { promptBuf = promptBuf.slice(0, -1) draw() continue } if (ev.type === 'key' && ev.ch && ev.ch !== '\n' && ev.ch !== '\t') { if (ev.ch >= ' ' && promptBuf.length < maxPromptLen) promptBuf += ev.ch draw() continue } continue } if (ev.type === 'paste') { bareEditInsertPasteText(buf, ev.text || '') draw() continue } if (ev.type === 'nav') { if (ev.key === 'pageup') { const { rows } = termDims() const step = Math.max(1, rows - 4) buf.row = Math.max(0, buf.row - step) bareEditClampCursor(buf) } else if (ev.key === 'pagedown') { const { rows } = termDims() const step = Math.max(1, rows - 4) buf.row = Math.min(buf.lines.length - 1, buf.row + step) bareEditClampCursor(buf) } else bareEditMoveKey(buf, ev.key) draw() continue } if (ev.type === 'ctrl') { if (ev.code === 'backspace') { bareEditBackspace(buf) draw() continue } if (ev.code === 'delete') { bareEditDelete(buf) draw() continue } if (ev.code === 'interrupt') { /* ignore in edit */ continue } const code = typeof ev.code === 'number' ? ev.code : 0 if (code === 27) { /* lone ESC fragment; do not insert */ continue } if (code === 24) { if (buf.dirty) { mode = 'quit_confirm' draw() } else break continue } if (code === 19) { await doSave(savePath) draw() continue } if (code === 15) { mode = 'prompt_saveas' promptTitle = 'File: ' promptBuf = savePath draw() continue } if (code === 23) { mode = 'prompt_search' promptTitle = 'Search: ' promptBuf = lastSearch draw() continue } if (code === 7) { mode = 'help' draw() continue } if (code === 31) { mode = 'prompt_goto' promptTitle = 'Go to line: ' promptBuf = '' draw() continue } if (code === 26) { bareEditUndo(buf) draw() continue } if (code === 25) { bareEditRedo(buf) draw() continue } continue } if (ev.type === 'key' && ev.ch) { if (ev.ch === '\n') bareEditNewline(buf) else if (ev.ch === '\t') bareEditInsertChar(buf, ' ') else bareEditInsertChar(buf, ev.ch) draw() } } } finally { try { if (useBracketPaste) { bareEditWrite(ctx, stdout, '\x1b[?2004l') } if (useAltScreen) { bareEditWrite(ctx, stdout, '\x1b[?1049l') } else { bareEditWrite(ctx, stdout, '\x1b[2J\x1b[H') } bareEditWrite(ctx, stdout, '\x1b[?25h\x1b[0m') } catch { /* ignore */ } reader.dispose() try { if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false) } catch { /* ignore */ } if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') { ctx.resumeReplAfterSubprocess() } } }