File size: 5,069 Bytes
0dbc9de | 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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { TextAttributes } from "@opentui/core"
import { useKeyboard } from "@opentui/solid"
import type { VcsFileStatus } from "@opencode-ai/sdk/v2"
import { createMemo, For } from "solid-js"
import { createStore } from "solid-js/store"
import { Locale } from "../util/locale"
import { useTheme } from "../context/theme"
import { useTuiConfig } from "../config"
import { useDialog, type DialogContext } from "../ui/dialog"
import { getScrollAcceleration } from "../util/scroll"
const options = ["no", "yes"] as const
export type WorkspaceFileChangesChoice = (typeof options)[number]
function statusLabel(status: VcsFileStatus["status"]) {
if (status === "added") return "A"
if (status === "deleted") return "D"
return "M"
}
function changeCountWidth(file: VcsFileStatus) {
// The "plus 2" is for spaces
return `${file.additions ? `+${file.additions}` : ""}${file.deletions ? ` -${file.deletions}` : ""}`.length + 2
}
export function DialogWorkspaceFileChanges(props: {
files: VcsFileStatus[]
onSelect: (choice: WorkspaceFileChangesChoice) => void
title?: string
message?: string
}) {
const dialog = useDialog()
const { theme } = useTheme()
const tuiConfig = useTuiConfig()
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
const [store, setStore] = createStore({ active: "yes" as WorkspaceFileChangesChoice })
const height = createMemo(() => Math.min(props.files.length, 8))
const fileNameWidth = createMemo(() => 48 - Math.max(Math.max(7, ...props.files.map(changeCountWidth)) - 7, 0))
function confirm() {
props.onSelect(store.active)
dialog.clear()
}
useKeyboard((evt) => {
if (evt.name === "return") {
evt.preventDefault()
evt.stopPropagation()
confirm()
return
}
if (evt.name === "left") {
evt.preventDefault()
evt.stopPropagation()
const index = options.indexOf(store.active)
setStore("active", options[Math.max(index - 1, 0)])
return
}
if (evt.name === "right") {
evt.preventDefault()
evt.stopPropagation()
const index = options.indexOf(store.active)
setStore("active", options[Math.min(index + 1, options.length - 1)])
}
})
return (
<box gap={1}>
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
<text attributes={TextAttributes.BOLD} fg={theme.text}>
{props.title ?? "File Changes Found"}
</text>
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
esc
</text>
</box>
<box paddingLeft={2} paddingRight={2}>
<text fg={theme.textMuted} wrapMode="word">
{props.message ?? "Do you want to move these changes with the session?"}
</text>
</box>
<scrollbox
height={height()}
backgroundColor={theme.backgroundElement}
scrollbarOptions={{ visible: false }}
scrollAcceleration={scrollAcceleration()}
>
<For each={props.files}>
{(item) => (
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
<box flexDirection="row" minWidth={0} flexShrink={1}>
<box width={2} flexShrink={0}>
<text fg={theme.textMuted}>{statusLabel(item.status)}</text>
</box>
<text fg={theme.textMuted} wrapMode="none">
{Locale.truncateLeft(item.file, fileNameWidth())}
</text>
</box>
<box flexDirection="row" gap={1} minWidth={7} flexShrink={0} justifyContent="flex-end">
<text>
{" "}
{item.additions ? <span style={{ fg: theme.diffAdded }}>+{item.additions}</span> : null}
{item.deletions ? <span style={{ fg: theme.diffRemoved }}> -{item.deletions}</span> : null}
</text>
</box>
</box>
)}
</For>
</scrollbox>
<box flexDirection="row" justifyContent="flex-end" paddingLeft={2} paddingRight={2} paddingBottom={1}>
<For each={options}>
{(item) => (
<box
paddingLeft={2}
paddingRight={2}
backgroundColor={item === store.active ? theme.primary : undefined}
onMouseUp={() => {
setStore("active", item)
props.onSelect(item)
dialog.clear()
}}
>
<text fg={item === store.active ? theme.selectedListItemText : theme.textMuted}>{item}</text>
</box>
)}
</For>
</box>
</box>
)
}
DialogWorkspaceFileChanges.show = (
dialog: DialogContext,
files: VcsFileStatus[],
options?: { title?: string; message?: string },
) => {
return new Promise<WorkspaceFileChangesChoice | undefined>((resolve) => {
dialog.replace(
() => <DialogWorkspaceFileChanges files={files} onSelect={resolve} {...options} />,
() => resolve(undefined),
)
})
}
|