Skip to content

Commit f6cc228

Browse files
authored
feat: unwrap cli-tui namespaces to flat exports + barrel (#22759)
1 parent 9f4b73b commit f6cc228

14 files changed

Lines changed: 394 additions & 399 deletions

File tree

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
2-
import { Clipboard } from "@tui/util/clipboard"
3-
import { Selection } from "@tui/util/selection"
4-
import { Terminal } from "@tui/util/terminal"
2+
import * as Clipboard from "@tui/util/clipboard"
3+
import * as Selection from "@tui/util/selection"
4+
import * as Terminal from "@tui/util/terminal"
55
import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core"
66
import { RouteProvider, useRoute } from "@tui/context/route"
77
import {

packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { TextAttributes } from "@opentui/core"
1111
import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@opencode-ai/sdk/v2"
1212
import { DialogModel } from "./dialog-model"
1313
import { useKeyboard } from "@opentui/solid"
14-
import { Clipboard } from "@tui/util/clipboard"
14+
import * as Clipboard from "@tui/util/clipboard"
1515
import { useToast } from "../ui/toast"
1616
import { isConsoleManagedProvider } from "@tui/util/provider-origin"
1717

packages/opencode/src/cli/cmd/tui/component/error-component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TextAttributes } from "@opentui/core"
22
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
3-
import { Clipboard } from "@tui/util/clipboard"
3+
import * as Clipboard from "@tui/util/clipboard"
44
import { createSignal } from "solid-js"
55
import { Installation } from "@/installation"
66
import { win32FlushInputBuffer } from "../win32"

packages/opencode/src/cli/cmd/tui/component/logo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BoxRenderable, MouseButton, MouseEvent, RGBA, TextAttributes } from "@opentui/core"
22
import { For, createMemo, createSignal, onCleanup, type JSX } from "solid-js"
33
import { useTheme, tint } from "@tui/context/theme"
4-
import { Sound } from "@tui/util/sound"
4+
import * as Sound from "@tui/util/sound"
55
import { logo } from "@/cli/logo"
66

77
// Shadow markers (rendered chars in parens):

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import { DialogStash } from "../dialog-stash"
2121
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
2222
import { useCommandDialog } from "../dialog-command"
2323
import { useRenderer, type JSX } from "@opentui/solid"
24-
import { Editor } from "@tui/util/editor"
24+
import * as Editor from "@tui/util/editor"
2525
import { useExit } from "../../context/exit"
26-
import { Clipboard } from "../../util/clipboard"
26+
import * as Clipboard from "../../util/clipboard"
2727
import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v2"
2828
import { TuiEvent } from "../../event"
2929
import { iife } from "@/util/iife"

packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useSync } from "@tui/context/sync"
33
import { DialogSelect } from "@tui/ui/dialog-select"
44
import { useSDK } from "@tui/context/sdk"
55
import { useRoute } from "@tui/context/route"
6-
import { Clipboard } from "@tui/util/clipboard"
6+
import * as Clipboard from "@tui/util/clipboard"
77
import type { PromptInfo } from "@tui/component/prompt/history"
88
import { strip } from "@tui/component/prompt/part"
99

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ import { SubagentFooter } from "./subagent-footer.tsx"
6666
import { Flag } from "@/flag/flag"
6767
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
6868
import parsers from "../../../../../../parsers-config.ts"
69-
import { Clipboard } from "../../util/clipboard"
69+
import * as Clipboard from "../../util/clipboard"
7070
import { Toast, useToast } from "../../ui/toast"
7171
import { useKV } from "../../context/kv.tsx"
72-
import { Editor } from "../../util/editor"
72+
import * as Editor from "../../util/editor"
7373
import stripAnsi from "strip-ansi"
7474
import { usePromptRef } from "../../context/prompt"
7575
import { useExit } from "../../context/exit"

packages/opencode/src/cli/cmd/tui/ui/dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MouseButton, Renderable, RGBA } from "@opentui/core"
55
import { createStore } from "solid-js/store"
66
import { useToast } from "./toast"
77
import { Flag } from "@/flag/flag"
8-
import { Selection } from "@tui/util/selection"
8+
import * as Selection from "@tui/util/selection"
99

1010
export function Dialog(
1111
props: ParentProps<{

packages/opencode/src/cli/cmd/tui/util/clipboard.ts

Lines changed: 136 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -22,171 +22,169 @@ function writeOsc52(text: string): void {
2222
process.stdout.write(sequence)
2323
}
2424

25-
export namespace Clipboard {
26-
export interface Content {
27-
data: string
28-
mime: string
29-
}
25+
export interface Content {
26+
data: string
27+
mime: string
28+
}
3029

31-
// Checks clipboard for images first, then falls back to text.
32-
//
33-
// On Windows prompt/ can call this from multiple paste signals because
34-
// terminals surface image paste differently:
35-
// 1. A forwarded Ctrl+V keypress
36-
// 2. An empty bracketed-paste hint for image-only clipboard in Windows
37-
// Terminal <1.25
38-
// 3. A kitty Ctrl+V key-release fallback for Windows Terminal 1.25+
39-
export async function read(): Promise<Content | undefined> {
40-
const os = platform()
30+
// Checks clipboard for images first, then falls back to text.
31+
//
32+
// On Windows prompt/ can call this from multiple paste signals because
33+
// terminals surface image paste differently:
34+
// 1. A forwarded Ctrl+V keypress
35+
// 2. An empty bracketed-paste hint for image-only clipboard in Windows
36+
// Terminal <1.25
37+
// 3. A kitty Ctrl+V key-release fallback for Windows Terminal 1.25+
38+
export async function read(): Promise<Content | undefined> {
39+
const os = platform()
4140

42-
if (os === "darwin") {
43-
const tmpfile = path.join(tmpdir(), "opencode-clipboard.png")
44-
try {
45-
await Process.run(
46-
[
47-
"osascript",
48-
"-e",
49-
'set imageData to the clipboard as "PNGf"',
50-
"-e",
51-
`set fileRef to open for access POSIX file "${tmpfile}" with write permission`,
52-
"-e",
53-
"set eof fileRef to 0",
54-
"-e",
55-
"write imageData to fileRef",
56-
"-e",
57-
"close access fileRef",
58-
],
59-
{ nothrow: true },
60-
)
61-
const buffer = await Filesystem.readBytes(tmpfile)
62-
return { data: buffer.toString("base64"), mime: "image/png" }
63-
} catch {
64-
} finally {
65-
await fs.rm(tmpfile, { force: true }).catch(() => {})
66-
}
41+
if (os === "darwin") {
42+
const tmpfile = path.join(tmpdir(), "opencode-clipboard.png")
43+
try {
44+
await Process.run(
45+
[
46+
"osascript",
47+
"-e",
48+
'set imageData to the clipboard as "PNGf"',
49+
"-e",
50+
`set fileRef to open for access POSIX file "${tmpfile}" with write permission`,
51+
"-e",
52+
"set eof fileRef to 0",
53+
"-e",
54+
"write imageData to fileRef",
55+
"-e",
56+
"close access fileRef",
57+
],
58+
{ nothrow: true },
59+
)
60+
const buffer = await Filesystem.readBytes(tmpfile)
61+
return { data: buffer.toString("base64"), mime: "image/png" }
62+
} catch {
63+
} finally {
64+
await fs.rm(tmpfile, { force: true }).catch(() => {})
6765
}
66+
}
6867

69-
// Windows/WSL: probe clipboard for images via PowerShell.
70-
// Bracketed paste can't carry image data so we read it directly.
71-
if (os === "win32" || release().includes("WSL")) {
72-
const script =
73-
"Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"
74-
const base64 = await Process.text(["powershell.exe", "-NonInteractive", "-NoProfile", "-command", script], {
75-
nothrow: true,
76-
})
77-
if (base64.text) {
78-
const imageBuffer = Buffer.from(base64.text.trim(), "base64")
79-
if (imageBuffer.length > 0) {
80-
return { data: imageBuffer.toString("base64"), mime: "image/png" }
81-
}
68+
// Windows/WSL: probe clipboard for images via PowerShell.
69+
// Bracketed paste can't carry image data so we read it directly.
70+
if (os === "win32" || release().includes("WSL")) {
71+
const script =
72+
"Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"
73+
const base64 = await Process.text(["powershell.exe", "-NonInteractive", "-NoProfile", "-command", script], {
74+
nothrow: true,
75+
})
76+
if (base64.text) {
77+
const imageBuffer = Buffer.from(base64.text.trim(), "base64")
78+
if (imageBuffer.length > 0) {
79+
return { data: imageBuffer.toString("base64"), mime: "image/png" }
8280
}
8381
}
82+
}
8483

85-
if (os === "linux") {
86-
const wayland = await Process.run(["wl-paste", "-t", "image/png"], { nothrow: true })
87-
if (wayland.stdout.byteLength > 0) {
88-
return { data: Buffer.from(wayland.stdout).toString("base64"), mime: "image/png" }
89-
}
90-
const x11 = await Process.run(["xclip", "-selection", "clipboard", "-t", "image/png", "-o"], {
91-
nothrow: true,
92-
})
93-
if (x11.stdout.byteLength > 0) {
94-
return { data: Buffer.from(x11.stdout).toString("base64"), mime: "image/png" }
95-
}
84+
if (os === "linux") {
85+
const wayland = await Process.run(["wl-paste", "-t", "image/png"], { nothrow: true })
86+
if (wayland.stdout.byteLength > 0) {
87+
return { data: Buffer.from(wayland.stdout).toString("base64"), mime: "image/png" }
9688
}
97-
98-
const text = await clipboardy.read().catch(() => {})
99-
if (text) {
100-
return { data: text, mime: "text/plain" }
89+
const x11 = await Process.run(["xclip", "-selection", "clipboard", "-t", "image/png", "-o"], {
90+
nothrow: true,
91+
})
92+
if (x11.stdout.byteLength > 0) {
93+
return { data: Buffer.from(x11.stdout).toString("base64"), mime: "image/png" }
10194
}
10295
}
10396

104-
const getCopyMethod = lazy(() => {
105-
const os = platform()
97+
const text = await clipboardy.read().catch(() => {})
98+
if (text) {
99+
return { data: text, mime: "text/plain" }
100+
}
101+
}
106102

107-
if (os === "darwin" && which("osascript")) {
108-
console.log("clipboard: using osascript")
109-
return async (text: string) => {
110-
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
111-
await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`], { nothrow: true })
112-
}
103+
const getCopyMethod = lazy(() => {
104+
const os = platform()
105+
106+
if (os === "darwin" && which("osascript")) {
107+
console.log("clipboard: using osascript")
108+
return async (text: string) => {
109+
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
110+
await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`], { nothrow: true })
113111
}
112+
}
114113

115-
if (os === "linux") {
116-
if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
117-
console.log("clipboard: using wl-copy")
118-
return async (text: string) => {
119-
const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
120-
if (!proc.stdin) return
121-
proc.stdin.write(text)
122-
proc.stdin.end()
123-
await proc.exited.catch(() => {})
124-
}
125-
}
126-
if (which("xclip")) {
127-
console.log("clipboard: using xclip")
128-
return async (text: string) => {
129-
const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
130-
stdin: "pipe",
131-
stdout: "ignore",
132-
stderr: "ignore",
133-
})
134-
if (!proc.stdin) return
135-
proc.stdin.write(text)
136-
proc.stdin.end()
137-
await proc.exited.catch(() => {})
138-
}
114+
if (os === "linux") {
115+
if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
116+
console.log("clipboard: using wl-copy")
117+
return async (text: string) => {
118+
const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
119+
if (!proc.stdin) return
120+
proc.stdin.write(text)
121+
proc.stdin.end()
122+
await proc.exited.catch(() => {})
139123
}
140-
if (which("xsel")) {
141-
console.log("clipboard: using xsel")
142-
return async (text: string) => {
143-
const proc = Process.spawn(["xsel", "--clipboard", "--input"], {
144-
stdin: "pipe",
145-
stdout: "ignore",
146-
stderr: "ignore",
147-
})
148-
if (!proc.stdin) return
149-
proc.stdin.write(text)
150-
proc.stdin.end()
151-
await proc.exited.catch(() => {})
152-
}
124+
}
125+
if (which("xclip")) {
126+
console.log("clipboard: using xclip")
127+
return async (text: string) => {
128+
const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
129+
stdin: "pipe",
130+
stdout: "ignore",
131+
stderr: "ignore",
132+
})
133+
if (!proc.stdin) return
134+
proc.stdin.write(text)
135+
proc.stdin.end()
136+
await proc.exited.catch(() => {})
153137
}
154138
}
155-
156-
if (os === "win32") {
157-
console.log("clipboard: using powershell")
139+
if (which("xsel")) {
140+
console.log("clipboard: using xsel")
158141
return async (text: string) => {
159-
// Pipe via stdin to avoid PowerShell string interpolation ($env:FOO, $(), etc.)
160-
const proc = Process.spawn(
161-
[
162-
"powershell.exe",
163-
"-NonInteractive",
164-
"-NoProfile",
165-
"-Command",
166-
"[Console]::InputEncoding = [System.Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())",
167-
],
168-
{
169-
stdin: "pipe",
170-
stdout: "ignore",
171-
stderr: "ignore",
172-
},
173-
)
174-
142+
const proc = Process.spawn(["xsel", "--clipboard", "--input"], {
143+
stdin: "pipe",
144+
stdout: "ignore",
145+
stderr: "ignore",
146+
})
175147
if (!proc.stdin) return
176148
proc.stdin.write(text)
177149
proc.stdin.end()
178150
await proc.exited.catch(() => {})
179151
}
180152
}
153+
}
181154

182-
console.log("clipboard: no native support")
155+
if (os === "win32") {
156+
console.log("clipboard: using powershell")
183157
return async (text: string) => {
184-
await clipboardy.write(text).catch(() => {})
158+
// Pipe via stdin to avoid PowerShell string interpolation ($env:FOO, $(), etc.)
159+
const proc = Process.spawn(
160+
[
161+
"powershell.exe",
162+
"-NonInteractive",
163+
"-NoProfile",
164+
"-Command",
165+
"[Console]::InputEncoding = [System.Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())",
166+
],
167+
{
168+
stdin: "pipe",
169+
stdout: "ignore",
170+
stderr: "ignore",
171+
},
172+
)
173+
174+
if (!proc.stdin) return
175+
proc.stdin.write(text)
176+
proc.stdin.end()
177+
await proc.exited.catch(() => {})
185178
}
186-
})
179+
}
187180

188-
export async function copy(text: string): Promise<void> {
189-
writeOsc52(text)
190-
await getCopyMethod()(text)
181+
console.log("clipboard: no native support")
182+
return async (text: string) => {
183+
await clipboardy.write(text).catch(() => {})
191184
}
185+
})
186+
187+
export async function copy(text: string): Promise<void> {
188+
writeOsc52(text)
189+
await getCopyMethod()(text)
192190
}

0 commit comments

Comments
 (0)