OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / distribute / hypertps-win64 / lib / screen.js

105 lines · javascript ref:
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)
  }
}