« get me outta code hell

Flushable.js « util - tui-lib - Pure Node.js library for making visual command-line programs (ala vim, ncdu)
about summary refs log tree commit diff
path: root/util/Flushable.js
blob: c8524216542104204edb68667b7319b6bfa917ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const ansi = require('./ansi')

module.exports = class Flushable {
  // A writable that can be used to collect chunks of data before writing
  // them.

  constructor(writable, shouldCompress = false) {
    this.target = writable

    // Use the magical ANSI self-made compression method that probably
    // doesn't *quite* work but should drastically decrease write size?
    this.shouldCompress = shouldCompress

    // Whether or not to show compression statistics (original written size
    // and ANSI-interpreted compressed size) in the output of flush.
    this.shouldShowCompressionStatistics = false

    // Update these if you plan on using the ANSI compressor!
    this.screenLines = 24
    this.screenCols = 80

    this.ended = false
    this.paused = false
    this.requestedFlush = false

    this.chunks = []
  }

  write(what) {
    this.chunks.push(what)
  }

  flush() {
    // If we're paused, we don't want to write, but we will keep a note that a
    // flush was requested for when we unpause.
    if (this.paused) {
      this.requestedFlush = true
      return
    }

    // Don't write if we've ended.
    if (this.ended) {
      return
    }

    // End if the target is destroyed.
    // Yes, this relies on the target having a destroyed property
    // Don't worry, it'll still work if there is no destroyed property though
    // (I think)
    if (this.target.destroyed) {
      this.end()
      return
    }

    let toWrite = this.chunks.join('')

    if (this.shouldCompress) {
      toWrite = this.compress(toWrite)
    }

    try {
      this.target.write(toWrite)
    } catch(err) {
      console.error('Flushable write error (ending):', err.message)
      this.end()
    }

    this.chunks = []
  }

  pause() {
    this.paused = true
  }

  resume() {
    this.paused = false

    if (this.requestedFlush) {
      this.flush()
    }
  }

  end() {
    this.ended = true
  }

  compress(toWrite) {
    // TODO: customize screen size
    const output = ansi.interpret(
      toWrite, this.screenLines, this.screenCols, this.lastFrame
    )

    let { screen } = output

    this.lastFrame = output

    if (this.shouldShowCompressionStatistics) {
      const pcSaved = Math.round(100 - (100 / toWrite.length * screen.length))
      screen += (
        '\x1b[H\x1b[0m(ANSI-interpret: ' +
        `${toWrite.length} -> ${screen.length} ${pcSaved}% saved)  `
      )
      this.lastFrame.oldLastChar.attributes = []
    }

    return screen
  }
}