summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/testing/terminal.ts
blob: 2bca39b31cdf9c052373f14efe4779fa465210ff (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
109
110
111
112
113
114
115
116
117
118
import type { ModelProbeState } from "./model-selection"

export const terminalAttr = "data-pty-id"

export type TerminalProbeState = {
  connected: boolean
  connects: number
  rendered: string
  settled: number
  focusing: number
}

type TerminalProbeControl = {
  disconnect?: VoidFunction
}

export type E2EWindow = Window & {
  __opencode_e2e?: {
    model?: {
      enabled?: boolean
      current?: ModelProbeState
    }
    prompt?: {
      enabled?: boolean
      current?: import("./prompt").PromptProbeState
    }
    terminal?: {
      enabled?: boolean
      terminals?: Record<string, TerminalProbeState>
      controls?: Record<string, TerminalProbeControl>
    }
  }
}

const seed = (): TerminalProbeState => ({
  connected: false,
  connects: 0,
  rendered: "",
  settled: 0,
  focusing: 0,
})

const root = () => {
  if (typeof window === "undefined") return
  const state = (window as E2EWindow).__opencode_e2e?.terminal
  if (!state?.enabled) return
  return state
}

const terms = () => {
  const state = root()
  if (!state) return
  state.terminals ??= {}
  return state.terminals
}

const controls = () => {
  const state = root()
  if (!state) return
  state.controls ??= {}
  return state.controls
}

export const terminalProbe = (id: string) => {
  const set = (next: Partial<TerminalProbeState>) => {
    const state = terms()
    if (!state) return
    state[id] = { ...(state[id] ?? seed()), ...next }
  }

  return {
    init() {
      set(seed())
    },
    connect() {
      const state = terms()
      if (!state) return
      const prev = state[id] ?? seed()
      state[id] = {
        ...prev,
        connected: true,
        connects: prev.connects + 1,
      }
    },
    render(data: string) {
      const state = terms()
      if (!state) return
      const prev = state[id] ?? seed()
      state[id] = { ...prev, rendered: prev.rendered + data }
    },
    settle() {
      const state = terms()
      if (!state) return
      const prev = state[id] ?? seed()
      state[id] = { ...prev, settled: prev.settled + 1 }
    },
    focus(count: number) {
      set({ focusing: Math.max(0, count) })
    },
    step() {
      const state = terms()
      if (!state) return
      const prev = state[id] ?? seed()
      state[id] = { ...prev, focusing: Math.max(0, prev.focusing - 1) }
    },
    control(next: Partial<TerminalProbeControl>) {
      const state = controls()
      if (!state) return
      state[id] = { ...(state[id] ?? {}), ...next }
    },
    drop() {
      const state = terms()
      if (state) delete state[id]
      const control = controls()
      if (control) delete control[id]
    },
  }
}