Skip to content

Commit e226bc2

Browse files
rekram1-nodeanntnzrb
authored andcommitted
feat: better styling for small screens (short and/or not wide) (anomalyco#5968)
1 parent 44f5f8d commit e226bc2

4 files changed

Lines changed: 258 additions & 193 deletions

File tree

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

Lines changed: 125 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Keybind } from "@/util/keybind"
1414
import { usePromptHistory, type PromptInfo } from "./history"
1515
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
1616
import { useCommandDialog } from "../dialog-command"
17-
import { useRenderer } from "@opentui/solid"
17+
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
1818
import { Editor } from "@tui/util/editor"
1919
import { useExit } from "../../context/exit"
2020
import { Clipboard } from "../../util/clipboard"
@@ -120,6 +120,9 @@ export function Prompt(props: PromptProps) {
120120
const history = usePromptHistory()
121121
const command = useCommandDialog()
122122
const renderer = useRenderer()
123+
const dimensions = useTerminalDimensions()
124+
const tall = createMemo(() => dimensions().height > 40)
125+
const wide = createMemo(() => dimensions().width > 120)
123126
const { theme, syntax } = useTheme()
124127

125128
function promptModelWarning() {
@@ -881,19 +884,21 @@ export function Prompt(props: PromptProps) {
881884
cursorColor={theme.text}
882885
syntaxStyle={syntax()}
883886
/>
884-
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
885-
<text fg={highlight()}>
886-
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
887-
</text>
888-
<Show when={store.mode === "normal"}>
889-
<box flexDirection="row" gap={1}>
890-
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
891-
{local.model.parsed().model}
892-
</text>
893-
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
894-
</box>
895-
</Show>
896-
</box>
887+
<Show when={tall()}>
888+
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
889+
<text fg={highlight()}>
890+
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
891+
</text>
892+
<Show when={store.mode === "normal"}>
893+
<box flexDirection="row" gap={1}>
894+
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
895+
{local.model.parsed().model}
896+
</text>
897+
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
898+
</box>
899+
</Show>
900+
</box>
901+
</Show>
897902
</box>
898903
</box>
899904
<box
@@ -923,101 +928,123 @@ export function Prompt(props: PromptProps) {
923928
/>
924929
</box>
925930
<box flexDirection="row" justifyContent="space-between">
926-
<Show when={status().type !== "idle"} fallback={<text />}>
927-
<box
928-
flexDirection="row"
929-
gap={1}
930-
flexGrow={1}
931-
justifyContent={status().type === "retry" ? "space-between" : "flex-start"}
932-
>
933-
<box flexShrink={0} flexDirection="row" gap={1}>
934-
{/* @ts-ignore // SpinnerOptions doesn't support marginLeft */}
935-
<spinner marginLeft={1} color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
936-
<box flexDirection="row" gap={1} flexShrink={0}>
937-
{(() => {
938-
const retry = createMemo(() => {
939-
const s = status()
940-
if (s.type !== "retry") return
941-
return s
942-
})
943-
const message = createMemo(() => {
944-
const r = retry()
945-
if (!r) return
946-
if (r.message.includes("exceeded your current quota") && r.message.includes("gemini"))
947-
return "gemini is way too hot right now"
948-
if (r.message.length > 80) return r.message.slice(0, 80) + "..."
949-
return r.message
950-
})
951-
const isTruncated = createMemo(() => {
952-
const r = retry()
953-
if (!r) return false
954-
return r.message.length > 120
955-
})
956-
const [seconds, setSeconds] = createSignal(0)
957-
onMount(() => {
958-
const timer = setInterval(() => {
959-
const next = retry()?.next
960-
if (next) setSeconds(Math.round((next - Date.now()) / 1000))
961-
}, 1000)
962-
963-
onCleanup(() => {
964-
clearInterval(timer)
931+
<Switch>
932+
<Match when={status().type !== "idle"}>
933+
<box
934+
flexDirection="row"
935+
gap={1}
936+
flexGrow={1}
937+
justifyContent={status().type === "retry" ? "space-between" : "flex-start"}
938+
>
939+
<box flexShrink={0} flexDirection="row" gap={1}>
940+
{/* @ts-ignore // SpinnerOptions doesn't support marginLeft */}
941+
<spinner marginLeft={1} color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
942+
<box flexDirection="row" gap={1} flexShrink={0}>
943+
{(() => {
944+
const retry = createMemo(() => {
945+
const s = status()
946+
if (s.type !== "retry") return
947+
return s
965948
})
966-
})
967-
const handleMessageClick = () => {
968-
const r = retry()
969-
if (!r) return
970-
if (isTruncated()) {
971-
DialogAlert.show(dialog, "Retry Error", r.message)
949+
const message = createMemo(() => {
950+
const r = retry()
951+
if (!r) return
952+
if (r.message.includes("exceeded your current quota") && r.message.includes("gemini"))
953+
return "gemini is way too hot right now"
954+
if (r.message.length > 80) return r.message.slice(0, 80) + "..."
955+
return r.message
956+
})
957+
const isTruncated = createMemo(() => {
958+
const r = retry()
959+
if (!r) return false
960+
return r.message.length > 120
961+
})
962+
const [seconds, setSeconds] = createSignal(0)
963+
onMount(() => {
964+
const timer = setInterval(() => {
965+
const next = retry()?.next
966+
if (next) setSeconds(Math.round((next - Date.now()) / 1000))
967+
}, 1000)
968+
969+
onCleanup(() => {
970+
clearTimeout(timer)
971+
})
972+
})
973+
const handleMessageClick = () => {
974+
const r = retry()
975+
if (!r) return
976+
if (isTruncated()) {
977+
DialogAlert.show(dialog, "Retry Error", r.message)
978+
}
972979
}
973-
}
974980

975-
const retryText = () => {
976-
const r = retry()
977-
if (!r) return ""
978-
const baseMessage = message()
979-
const truncatedHint = isTruncated() ? " (click to expand)" : ""
980-
const retryInfo = ` [retrying ${seconds() > 0 ? `in ${seconds()}s ` : ""}attempt #${r.attempt}]`
981-
return baseMessage + truncatedHint + retryInfo
982-
}
981+
const retryText = () => {
982+
const r = retry()
983+
if (!r) return ""
984+
const baseMessage = message()
985+
const truncatedHint = isTruncated() ? " (click to expand)" : ""
986+
const retryInfo = ` [retrying ${seconds() > 0 ? `in ${seconds()}s ` : ""}attempt #${r.attempt}]`
987+
return baseMessage + truncatedHint + retryInfo
988+
}
983989

984-
return (
985-
<Show when={retry()}>
986-
<box onMouseUp={handleMessageClick}>
987-
<text fg={theme.error}>{retryText()}</text>
988-
</box>
989-
</Show>
990-
)
991-
})()}
990+
return (
991+
<Show when={retry()}>
992+
<box onMouseUp={handleMessageClick}>
993+
<text fg={theme.error}>{retryText()}</text>
994+
</box>
995+
</Show>
996+
)
997+
})()}
998+
</box>
992999
</box>
1000+
<text fg={store.interrupt > 0 ? theme.primary : theme.text}>
1001+
esc{" "}
1002+
<span style={{ fg: store.interrupt > 0 ? theme.primary : theme.textMuted }}>
1003+
{store.interrupt > 0 ? "again to interrupt" : "interrupt"}
1004+
</span>
1005+
</text>
9931006
</box>
994-
<text fg={store.interrupt > 0 ? theme.primary : theme.text}>
995-
esc{" "}
996-
<span style={{ fg: store.interrupt > 0 ? theme.primary : theme.textMuted }}>
997-
{store.interrupt > 0 ? "again to interrupt" : "interrupt"}
998-
</span>
999-
</text>
1000-
</box>
1001-
</Show>
1002-
<Show when={status().type !== "retry"}>
1003-
<box gap={2} flexDirection="row">
1004-
<Switch>
1005-
<Match when={store.mode === "normal"}>
1007+
</Match>
1008+
<Match when={!tall()}>
1009+
<box flexDirection="row" gap={1}>
1010+
<text fg={highlight()}>
1011+
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
1012+
</text>
1013+
<Show when={store.mode === "normal"}>
1014+
<box flexDirection="row" gap={1}>
1015+
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
1016+
{local.model.parsed().model}
1017+
</text>
1018+
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
1019+
</box>
1020+
</Show>
1021+
</box>
1022+
</Match>
1023+
</Switch>
1024+
<box gap={2} flexDirection="row" marginLeft="auto">
1025+
<Switch>
1026+
<Match when={store.mode === "normal"}>
1027+
<Show when={wide()}>
10061028
<text fg={theme.text}>
10071029
{keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>switch agent</span>
10081030
</text>
1031+
</Show>
1032+
<Show when={!wide()}>
10091033
<text fg={theme.text}>
1010-
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
1011-
</text>
1012-
</Match>
1013-
<Match when={store.mode === "shell"}>
1014-
<text fg={theme.text}>
1015-
esc <span style={{ fg: theme.textMuted }}>exit shell mode</span>
1034+
{keybind.print("sidebar_toggle")} <span style={{ fg: theme.textMuted }}>sidebar</span>
10161035
</text>
1017-
</Match>
1018-
</Switch>
1019-
</box>
1020-
</Show>
1036+
</Show>
1037+
<text fg={theme.text}>
1038+
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
1039+
</text>
1040+
</Match>
1041+
<Match when={store.mode === "shell"}>
1042+
<text fg={theme.text}>
1043+
esc <span style={{ fg: theme.textMuted }}>exit shell mode</span>
1044+
</text>
1045+
</Match>
1046+
</Switch>
1047+
</box>
10211048
</box>
10221049
</box>
10231050
</>

0 commit comments

Comments
 (0)