All files / context / tui.ts

100.00% Branches 0/0
0.00% Lines 0/74
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
109
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 












































































































import type { Timed } from '../index.ts';

export const tuiContext = (
  input: TuiIn, output: TuiOut, state = {}
): Tui => ({
  exited: false,

  input,
  isTTY: input?.isTTY || false,
  isRaw: input?.isRaw || false,
  get [Symbol.asyncIterator] () { return input[Symbol.asyncIterator] },

  output,
  rows:     output?.columns || 80,
  columns:  output?.rows    || 25,
  width:    output?.columns || 80,
  height:   output?.rows    || 25,
  cursorTo: (...args) => output?.cursorTo(...args),
  write:    (...args) => output?.write(...args),

  ...state
}) as Tui;

export type Tui = FrameTimings & TuiIn & TuiOut & {
  exited:  boolean,
  input:   TuiIn,
  output:  TuiOut,
  width:   number,
  height:  number,
};

export type TuiIn = {
  [Symbol.asyncIterator] (): AsyncIterator<string|Uint8Array>
  isTTY: boolean,
  isRaw: boolean,
}

export type TuiOut = {
  rows,
  columns,
  cursorTo: (x: number, y: number) => void
  write:    (_: string)            => void
};

export type FrameTimings = Timed & {
  t1: number,
  tF: number,
  tS: number,
};

export const runInput = <T extends Tui> (
  state: T, handler: (state: T, chunk: string|Uint8Array)=>unknown
) => new Promise(async (resolve, reject)=>{
  try {
    while (!state.exited) {
      for await (const chunk of state.input) {
        if (await handler(state, chunk)) break;
      }
      if (state.exited) {
        break;
      }
    }
    resolve(true);
  } catch (e) {
    reject(e);
  }
})

export const runOutput = <T extends Tui> (
  state: T, ...steps: Array<string|((_: T)=>unknown)>
) => new Promise(async (resolve, reject)=>{
  try {
    console.clear();
    while (!state.exited) {
      state.t0 = performance.now();
      at(0, 0, ...steps)(state);
      state.t1 = performance.now();
      state.tF = (1000/25);
      state.tD = state.t1 - state.t0;
      state.tS = state.tF - state.tD;
      const wait = Math.max(0, state.tS);
      await new Promise(resolve=>setTimeout(resolve, wait));
    }
    resolve(true);
  } catch (e) {
    reject(e);
  }
});

export const at = <T extends Tui> (
  x: number, y: number, ...steps: Array<string|((_: T)=>unknown)>
) => Object.assign(function drawAt (state: Tui) {
  state.cursorTo(x, y);
  return draw(...steps)(state);
});

export const draw = <T extends Tui> (
  ...steps: Array<string|((_: T)=>unknown)>
) => Object.assign(async function draw (state: T) {
  for (const step of steps) {
    if (typeof step === 'string') {
      state.write(step);
    } else if (typeof step === 'function') {
      await step(state);
    } else if (step) {
      throw new Error('unsupported step');
    }
  }
  return state
});