summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context/terminal.tsx
blob: e9a07077cef879e5aea67694922f88866d3cabbd (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
119
120
121
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { batch, createMemo } from "solid-js"
import { useParams } from "@solidjs/router"
import { useSDK } from "./sdk"
import { persisted } from "@/utils/persist"

export type LocalPTY = {
  id: string
  title: string
  rows?: number
  cols?: number
  buffer?: string
  scrollY?: number
}

export const { use: useTerminal, provider: TerminalProvider } = createSimpleContext({
  name: "Terminal",
  init: () => {
    const sdk = useSDK()
    const params = useParams()
    const name = createMemo(() => `${params.dir}/terminal${params.id ? "/" + params.id : ""}.v1`)

    const [store, setStore, _, ready] = persisted(
      name(),
      createStore<{
        active?: string
        all: LocalPTY[]
      }>({
        all: [],
      }),
    )

    return {
      ready,
      all: createMemo(() => Object.values(store.all)),
      active: createMemo(() => store.active),
      new() {
        sdk.client.pty
          .create({ title: `Terminal ${store.all.length + 1}` })
          .then((pty) => {
            const id = pty.data?.id
            if (!id) return
            setStore("all", [
              ...store.all,
              {
                id,
                title: pty.data?.title ?? "Terminal",
              },
            ])
            setStore("active", id)
          })
          .catch((e) => {
            console.error("Failed to create terminal", e)
          })
      },
      update(pty: Partial<LocalPTY> & { id: string }) {
        setStore("all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
        sdk.client.pty
          .update({
            ptyID: pty.id,
            title: pty.title,
            size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
          })
          .catch((e) => {
            console.error("Failed to update terminal", e)
          })
      },
      async clone(id: string) {
        const index = store.all.findIndex((x) => x.id === id)
        const pty = store.all[index]
        if (!pty) return
        const clone = await sdk.client.pty
          .create({
            title: pty.title,
          })
          .catch((e) => {
            console.error("Failed to clone terminal", e)
            return undefined
          })
        if (!clone?.data) return
        setStore("all", index, {
          ...pty,
          ...clone.data,
        })
        if (store.active === pty.id) {
          setStore("active", clone.data.id)
        }
      },
      open(id: string) {
        setStore("active", id)
      },
      async close(id: string) {
        batch(() => {
          setStore(
            "all",
            store.all.filter((x) => x.id !== id),
          )
          if (store.active === id) {
            const index = store.all.findIndex((f) => f.id === id)
            const previous = store.all[Math.max(0, index - 1)]
            setStore("active", previous?.id)
          }
        })
        await sdk.client.pty.remove({ ptyID: id }).catch((e) => {
          console.error("Failed to close terminal", e)
        })
      },
      move(id: string, to: number) {
        const index = store.all.findIndex((f) => f.id === id)
        if (index === -1) return
        setStore(
          "all",
          produce((all) => {
            all.splice(to, 0, all.splice(index, 1)[0])
          }),
        )
      },
    }
  },
})