refactor(tui): extract standalone package (#31193)
This commit is contained in:
@@ -10,7 +10,7 @@ import { useProject } from "../../context/project"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
import { useSync } from "../../context/sync"
|
||||
import { getScrollAcceleration } from "../../util/scroll"
|
||||
import { useTuiEnvironment } from "../../runtime"
|
||||
import { useTuiPaths } from "../../context/runtime"
|
||||
import { useTuiConfig } from "../../config"
|
||||
import { useTheme, selectedForeground } from "../../context/theme"
|
||||
import { SplitBorder } from "../../ui/border"
|
||||
@@ -92,7 +92,7 @@ export function Autocomplete(props: {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const frecency = useFrecency()
|
||||
const tuiConfig = useTuiConfig()
|
||||
const environment = useTuiEnvironment()
|
||||
const paths = useTuiPaths()
|
||||
const [store, setStore] = createStore({
|
||||
index: 0,
|
||||
selected: 0,
|
||||
@@ -236,7 +236,7 @@ export function Autocomplete(props: {
|
||||
}
|
||||
|
||||
function createFilePart(item: string, lineRange?: { startLine: number; endLine?: number }) {
|
||||
const baseDir = (sync.path.directory || environment.cwd).replace(/\/+$/, "")
|
||||
const baseDir = (sync.path.directory || paths.cwd).replace(/\/+$/, "")
|
||||
const fullPath = path.isAbsolute(item) ? item : path.join(baseDir, item)
|
||||
const urlObj = pathToFileURL(fullPath)
|
||||
const filename =
|
||||
@@ -306,7 +306,7 @@ export function Autocomplete(props: {
|
||||
})
|
||||
|
||||
function normalizeMentionPath(filePath: string) {
|
||||
const baseDir = sync.path.directory || environment.cwd
|
||||
const baseDir = sync.path.directory || paths.cwd
|
||||
const absolute = path.resolve(filePath)
|
||||
const relative = path.relative(baseDir, absolute)
|
||||
|
||||
|
||||
@@ -14,10 +14,11 @@ import "opentui-spinner/solid"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { useLocal } from "../../context/local"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { tint, useTheme } from "../../context/theme"
|
||||
import { EmptyBorder, SplitBorder } from "../../ui/border"
|
||||
import { useTuiEnvironment } from "../../runtime"
|
||||
import { useTuiPlatform } from "../../platform"
|
||||
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
|
||||
import { useClipboard } from "../../context/clipboard"
|
||||
import { Spinner } from "../spinner"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
import { useRoute } from "../../context/route"
|
||||
@@ -25,6 +26,8 @@ import { useProject } from "../../context/project"
|
||||
import { useSync } from "../../context/sync"
|
||||
import { useEvent } from "../../context/event"
|
||||
import { editorSelectionKey, useEditorContext, type EditorSelection } from "../../context/editor"
|
||||
import { openEditor } from "../../editor"
|
||||
import { destroyRenderer } from "../../util/renderer"
|
||||
import { promptOffsetWidth } from "../../prompt/display"
|
||||
import { createStore, produce, unwrap } from "solid-js/store"
|
||||
import { usePromptHistory, type PromptInfo } from "../../prompt/history"
|
||||
@@ -34,7 +37,6 @@ import { usePromptStash } from "../../prompt/stash"
|
||||
import { DialogStash } from "../dialog-stash"
|
||||
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
||||
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import { useExit } from "../../context/exit"
|
||||
import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { errorMessage } from "../../util/error"
|
||||
@@ -143,8 +145,9 @@ export function Prompt(props: PromptProps) {
|
||||
const leader = useLeaderActive()
|
||||
const local = useLocal()
|
||||
const args = useArgs()
|
||||
const environment = useTuiEnvironment()
|
||||
const platform = useTuiPlatform()
|
||||
const paths = useTuiPaths()
|
||||
const terminalEnvironment = useTuiTerminalEnvironment()
|
||||
const clipboard = useClipboard()
|
||||
const sdk = useSDK()
|
||||
const editor = useEditorContext()
|
||||
const route = useRoute()
|
||||
@@ -366,7 +369,7 @@ export function Prompt(props: PromptProps) {
|
||||
run: async (ctx: CommandContext<Renderable, KeyEvent>) => {
|
||||
ctx.event.preventDefault()
|
||||
ctx.event.stopPropagation()
|
||||
const content = await platform.clipboard?.read?.()
|
||||
const content = await clipboard.read?.()
|
||||
if (content?.mime.startsWith("image/")) {
|
||||
await pasteAttachment({
|
||||
filename: "clipboard",
|
||||
@@ -430,12 +433,13 @@ export function Prompt(props: PromptProps) {
|
||||
const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text")
|
||||
|
||||
const value = text
|
||||
const content = await platform.editor?.open({
|
||||
const content = await openEditor({
|
||||
renderer,
|
||||
value,
|
||||
cwd:
|
||||
(project.instance.path().worktree === "/" ? undefined : project.instance.path().worktree) ||
|
||||
project.instance.directory() ||
|
||||
environment.cwd,
|
||||
paths.cwd,
|
||||
})
|
||||
if (!content) return
|
||||
|
||||
@@ -526,7 +530,7 @@ export function Prompt(props: PromptProps) {
|
||||
desc: "Change the workspace for the session",
|
||||
name: "workspace.set",
|
||||
category: "Session",
|
||||
enabled: environment.capabilities.workspaces,
|
||||
enabled: Flag.OPENCODE_EXPERIMENTAL_WORKSPACES,
|
||||
slashName: "warp",
|
||||
run: () => {
|
||||
workspace.open()
|
||||
@@ -950,7 +954,7 @@ export function Prompt(props: PromptProps) {
|
||||
if (!agent) return false
|
||||
const trimmed = store.prompt.input.trim()
|
||||
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
|
||||
void exit()
|
||||
destroyRenderer(renderer)
|
||||
return true
|
||||
}
|
||||
const selectedModel = local.model.current()
|
||||
@@ -1124,7 +1128,6 @@ export function Prompt(props: PromptProps) {
|
||||
if (finishMoveProgress) move.finishSubmit()
|
||||
return true
|
||||
}
|
||||
const exit = useExit()
|
||||
|
||||
function pasteText(text: string, virtualText: string) {
|
||||
const currentOffset = input.cursorOffset
|
||||
@@ -1163,10 +1166,10 @@ export function Prompt(props: PromptProps) {
|
||||
async function pasteInputText(text: string) {
|
||||
const normalizedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n")
|
||||
const pastedContent = normalizedText.trim()
|
||||
const filepath = pastedFilepath(pastedContent, environment.platform)
|
||||
const filepath = pastedFilepath(pastedContent, terminalEnvironment.platform)
|
||||
const isUrl = /^(https?):\/\//.test(filepath)
|
||||
if (!isUrl) {
|
||||
const attachment = await readLocalAttachment(platform.files, filepath)
|
||||
const attachment = await readLocalAttachment(filepath)
|
||||
const filename = path.basename(filepath)
|
||||
if (attachment?.type === "text") {
|
||||
pasteText(attachment.content, `[SVG: ${filename ?? "image"}]`)
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
import type { PlatformFiles } from "../../platform"
|
||||
import { readFile } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
|
||||
export type LocalFiles = Readonly<{
|
||||
readText(path: string): Promise<string>
|
||||
readBytes(path: string): Promise<Uint8Array>
|
||||
mime(path: string): Promise<string>
|
||||
}>
|
||||
|
||||
export type LocalAttachment =
|
||||
| Readonly<{ type: "text"; mime: "image/svg+xml"; content: string }>
|
||||
| Readonly<{ type: "binary"; mime: string; content: Uint8Array }>
|
||||
|
||||
export async function readLocalAttachment(files: PlatformFiles, path: string): Promise<LocalAttachment | undefined> {
|
||||
export function readLocalAttachment(file: string) {
|
||||
return readLocalAttachmentWith(
|
||||
{
|
||||
readText: (value) => readFile(value, "utf8"),
|
||||
readBytes: (value) => readFile(value),
|
||||
mime: async (value) => mimeTypes[path.extname(value).toLowerCase()] ?? "application/octet-stream",
|
||||
},
|
||||
file,
|
||||
)
|
||||
}
|
||||
|
||||
const mimeTypes: Record<string, string> = {
|
||||
".avif": "image/avif",
|
||||
".gif": "image/gif",
|
||||
".jpeg": "image/jpeg",
|
||||
".jpg": "image/jpeg",
|
||||
".pdf": "application/pdf",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
".webp": "image/webp",
|
||||
}
|
||||
|
||||
export async function readLocalAttachmentWith(files: LocalFiles, path: string): Promise<LocalAttachment | undefined> {
|
||||
const mime = await files.mime(path).catch(() => undefined)
|
||||
if (!mime) return
|
||||
if (mime === "image/svg+xml") {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
||||
import path from "path"
|
||||
import { useTuiEnvironment } from "../../runtime"
|
||||
import { useTuiPaths } from "../../context/runtime"
|
||||
import { errorMessage } from "../../util/error"
|
||||
import { useDialog } from "../../ui/dialog"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
@@ -20,7 +20,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||
const sync = useSync()
|
||||
const toast = useToast()
|
||||
const homeDestination = useHomeSessionDestination()
|
||||
const environment = useTuiEnvironment()
|
||||
const paths = useTuiPaths()
|
||||
const [creating, setCreating] = createSignal(false)
|
||||
const [creatingDots, setCreatingDots] = createSignal(3)
|
||||
const [progress, setProgress] = createSignal<string>()
|
||||
@@ -35,7 +35,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||
{
|
||||
projectID,
|
||||
strategy: "git_worktree",
|
||||
directory: path.join(environment.paths.worktree, projectID.slice(0, 6)),
|
||||
directory: path.join(paths.worktree, projectID.slice(0, 6)),
|
||||
context,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
|
||||
Reference in New Issue
Block a user