diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index caa13032292f..79698901e972 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -713,6 +713,14 @@ export function Prompt(props: PromptProps) { return !!current }) + const cursorStyle = createMemo(() => { + const tui = sync.data.config.tui + return { + style: (tui?.cursor_style ?? "block") as "block" | "line" | "underline", + blinking: tui?.cursor_blink ?? true, + } + }) + const spinnerDef = createMemo(() => { const color = local.agent.color(local.agent.current().name) return { @@ -948,6 +956,7 @@ export function Prompt(props: PromptProps) { onMouseDown={(r: MouseEvent) => r.target?.focus()} focusedBackgroundColor={theme.backgroundElement} cursorColor={theme.text} + cursorStyle={cursorStyle()} syntaxStyle={syntax()} /> diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index 9e79c76bf518..43dfc4461c8c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -306,6 +306,15 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( const dimensions = useTerminalDimensions() const narrow = createMemo(() => dimensions().width < 80) const dialog = useDialog() + const sync = useSync() + + const cursorStyle = createMemo(() => { + const tui = sync.data.config.tui + return { + style: (tui?.cursor_style ?? "block") as "block" | "line" | "underline", + blinking: tui?.cursor_blink ?? true, + } + }) useKeyboard((evt) => { if (dialog.stack.length > 0) return @@ -355,6 +364,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( textColor={theme.text} focusedTextColor={theme.text} cursorColor={theme.primary} + cursorStyle={cursorStyle()} keyBindings={textareaKeybindings()} /> diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx index 88e99c6ea8b3..943084e6caed 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx @@ -4,6 +4,7 @@ import { useKeyboard } from "@opentui/solid" import type { TextareaRenderable } from "@opentui/core" import { useKeybind } from "../../context/keybind" import { selectedForeground, tint, useTheme } from "../../context/theme" +import { useSync } from "../../context/sync" import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2" import { useSDK } from "../../context/sdk" import { SplitBorder } from "../../component/border" @@ -15,6 +16,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { const { theme } = useTheme() const keybind = useKeybind() const bindings = useTextareaKeybindings() + const sync = useSync() const questions = createMemo(() => props.request.questions) const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true) @@ -41,6 +43,13 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { if (!value) return false return store.answers[store.tab]?.includes(value) ?? false }) + const cursorStyle = createMemo(() => { + const tui = sync.data.config.tui + return { + style: (tui?.cursor_style ?? "block") as "block" | "line" | "underline", + blinking: tui?.cursor_blink ?? true, + } + }) function submit() { const answers = questions().map((_, i) => store.answers[i] ?? []) @@ -379,6 +388,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { textColor={theme.text} focusedTextColor={theme.text} cursorColor={theme.primary} + cursorStyle={cursorStyle()} keyBindings={bindings()} /> diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx index 867ed6810028..6d257b31239a 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx @@ -2,8 +2,9 @@ import { TextareaRenderable, TextAttributes } from "@opentui/core" import { useTheme } from "../context/theme" import { useDialog, type DialogContext } from "./dialog" import { createStore } from "solid-js/store" -import { onMount, Show, type JSX } from "solid-js" +import { createMemo, onMount, Show, type JSX } from "solid-js" import { useKeyboard } from "@opentui/solid" +import { useSync } from "../context/sync" export type DialogExportOptionsProps = { defaultFilename: string @@ -24,7 +25,17 @@ export type DialogExportOptionsProps = { export function DialogExportOptions(props: DialogExportOptionsProps) { const dialog = useDialog() const { theme } = useTheme() + const sync = useSync() let textarea: TextareaRenderable + + const cursorStyle = createMemo(() => { + const tui = sync.data.config.tui + return { + style: (tui?.cursor_style ?? "block") as "block" | "line" | "underline", + blinking: tui?.cursor_blink ?? true, + } + }) + const [store, setStore] = createStore({ thinking: props.defaultThinking, toolDetails: props.defaultToolDetails, @@ -104,6 +115,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { textColor={theme.text} focusedTextColor={theme.text} cursorColor={theme.text} + cursorStyle={cursorStyle()} /> diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx index b29652412430..9893b86935ca 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx @@ -1,8 +1,9 @@ import { TextareaRenderable, TextAttributes } from "@opentui/core" import { useTheme } from "../context/theme" import { useDialog, type DialogContext } from "./dialog" -import { onMount, type JSX } from "solid-js" +import { createMemo, onMount, type JSX } from "solid-js" import { useKeyboard } from "@opentui/solid" +import { useSync } from "../context/sync" export type DialogPromptProps = { title: string @@ -16,8 +17,17 @@ export type DialogPromptProps = { export function DialogPrompt(props: DialogPromptProps) { const dialog = useDialog() const { theme } = useTheme() + const sync = useSync() let textarea: TextareaRenderable + const cursorStyle = createMemo(() => { + const tui = sync.data.config.tui + return { + style: (tui?.cursor_style ?? "block") as "block" | "line" | "underline", + blinking: tui?.cursor_blink ?? true, + } + }) + useKeyboard((evt) => { if (evt.name === "return") { props.onConfirm?.(textarea.plainText) @@ -55,6 +65,7 @@ export function DialogPrompt(props: DialogPromptProps) { textColor={theme.text} focusedTextColor={theme.text} cursorColor={theme.text} + cursorStyle={cursorStyle()} /> diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 7969e3079574..9ac79393ce8f 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -829,6 +829,11 @@ export namespace Config { ref: "KeybindsConfig", }) + export const CursorStyle = z.enum(["block", "line", "underline"]).meta({ + ref: "CursorStyleConfig", + }) + export type CursorStyle = z.infer + export const TUI = z.object({ scroll_speed: z.number().min(0.001).optional().describe("TUI scroll speed"), scroll_acceleration: z @@ -841,6 +846,8 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), + cursor_style: CursorStyle.optional().describe("Cursor shape in text inputs: 'block', 'line', or 'underline'"), + cursor_blink: z.boolean().optional().describe("Enable cursor blinking in text inputs"), }) export const Server = z diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index cb2f586775a1..8af4b6139733 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1315,6 +1315,11 @@ export type KeybindsConfig = { */ export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" +/** + * Cursor shape in text inputs: 'block', 'line', or 'underline' + */ +export type CursorStyleConfig = "block" | "line" | "underline" + /** * Server configuration for opencode serve and web commands */ @@ -1620,6 +1625,11 @@ export type Config = { * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column */ diff_style?: "auto" | "stacked" + cursor_style?: CursorStyleConfig + /** + * Enable cursor blinking in text inputs + */ + cursor_blink?: boolean } server?: ServerConfig /**