import tty from 'bare-tty'
import ansi from 'bare-ansi-escapes'
import KeyDecoder from 'bare-ansi-escapes/key-decoder'
import { EventEmitter } from 'events'
const ALT_SCREEN_ON = '\x1b[?1049h'
const ALT_SCREEN_OFF = '\x1b[?1049l'
export class Screen extends EventEmitter {
constructor () {
super()
if (!tty.isTTY(0) || !tty.isTTY(1)) {
throw new Error('keet-tui requires a terminal (TTY). Run interactively, not piped.')
}
this._stdin = new tty.ReadStream(0)
this._stdout = new tty.WriteStream(1)
this._decoder = new KeyDecoder()
this._raw = false
this._alt = false
this._lastFrame = null
}
get width () {
return this._stdout.columns
}
get height () {
return this._stdout.rows
}
enterAltScreen () {
if (this._alt) return
this._alt = true
this._write(ALT_SCREEN_ON)
}
exitAltScreen () {
if (!this._alt) return
this._alt = false
this._write(ALT_SCREEN_OFF)
}
enableRawMode () {
if (this._raw) return
this._raw = true
this._stdin.setRawMode(true)
this._decoder.on('data', (key) => {
this.emit('key', key)
})
this._stdin.pipe(this._decoder)
this._stdout.on('resize', () => {
this.emit('resize', { width: this.width, height: this.height })
})
}
disableRawMode () {
if (!this._raw) return
this._raw = false
if (typeof this._stdin.unpipe === 'function') {
this._stdin.unpipe(this._decoder)
}
this._stdin.setRawMode(false)
}
hideCursor () {
this._write(ansi.cursorHide)
}
showCursor () {
this._write(ansi.cursorShow)
}
render (lines) {
const frame = lines.join('\n')
if (frame === this._lastFrame) return
this._lastFrame = frame
let buf = ansi.cursorPosition(0, 0)
const h = this.height
for (let i = 0; i < h; i++) {
buf += ansi.cursorPosition(0, i)
buf += ansi.eraseLine
if (i < lines.length) {
buf += lines[i]
}
}
this._write(buf)
}
destroy () {
this.showCursor()
this.exitAltScreen()
this.disableRawMode()
this._stdin.destroy()
this._stdout.end()
}
_write (data) {
this._stdout.write(data)
}
}