tui: improve prompt handling, session interactions, and dialog focus management

This commit is contained in:
Dax Raad 2025-09-30 04:28:48 -04:00
parent dd18b766f3
commit 873cccd37c
5 changed files with 28 additions and 14 deletions

View file

@ -8,7 +8,8 @@
"typecheck": "tsc --noEmit",
"test": "bun test",
"build": "./script/build.ts",
"dev": "bun run --conditions=browser ./src/index.ts"
"dev": "bun run --conditions=browser ./src/index.ts",
"random": "echo 'This is a random script'"
},
"bin": {
"opencode": "./bin/opencode"

View file

@ -224,15 +224,17 @@ export function Autocomplete(props: {
if (e.name === "escape") hide()
if (e.name === "return") select()
}
if (!store.visible && e.name === "@") {
const last = props.value.at(-1)
if (last === " " || last === undefined) {
show("@")
if (!store.visible) {
if (e.name === "@") {
const last = props.value.at(-1)
if (last === " " || last === undefined) {
show("@")
}
}
}
if (!store.visible && e.name === "/") {
if (props.input().cursorPosition === 0) show("/")
if (e.name === "/") {
if (props.input().cursorPosition === 0) show("/")
}
}
},
})

View file

@ -33,7 +33,6 @@ export function Prompt(props: PromptProps) {
let anchor: BoxRenderable
let autocomplete: AutocompleteRef
const keybind = useKeybind()
const local = useLocal()
const sdk = useSDK()
@ -42,6 +41,11 @@ export function Prompt(props: PromptProps) {
const status = createMemo(() => (props.sessionID ? sync.session.status(props.sessionID) : "idle"))
const history = usePromptHistory()
createEffect(() => {
if (props.disabled) input.cursorColor = Theme.backgroundElement
if (!props.disabled) input.cursorColor = Theme.primary
})
const [store, setStore] = createStore<PromptInfo>({
input: "",
parts: [],
@ -129,6 +133,10 @@ export function Prompt(props: PromptProps) {
}}
value={store.input}
onKeyDown={async (e) => {
if (props.disabled) {
e.preventDefault()
return
}
autocomplete.onKeyDown(e)
if (!autocomplete.visible) {
if (e.name === "up" || e.name === "down") {
@ -166,6 +174,7 @@ export function Prompt(props: PromptProps) {
}, 0)
}}
onSubmit={async () => {
if (props.disabled) return
if (autocomplete.visible) return
if (!store.input) return
const sessionID = props.sessionID

View file

@ -177,7 +177,8 @@ export function Session() {
value: "session.redo",
keybind: "messages_redo",
category: "Session",
onSelect: () => {
onSelect: (dialog) => {
dialog.clear()
const messageID = session().revert?.messageID
if (!messageID) return
const message = messages().find((x) => x.role === "user" && x.id > messageID)
@ -295,7 +296,7 @@ export function Session() {
</Match>
<Match when={message.role === "user"}>
<UserMessage
onMouseDown={() =>
onMouseUp={() =>
dialog.replace(() => <DialogMessage messageID={message.id} sessionID={route.sessionID} />)
}
message={message as UserMessage}
@ -327,6 +328,7 @@ export function Session() {
<box flexShrink={0}>
<Prompt
ref={(r) => (prompt = r)}
disabled={permissions().length > 0}
onSubmit={() => {
toBottom()
}}
@ -347,7 +349,7 @@ const MIME_BADGE: Record<string, string> = {
"application/pdf": "pdf",
}
function UserMessage(props: { message: UserMessage; parts: Part[]; onMouseDown: () => void }) {
function UserMessage(props: { message: UserMessage; parts: Part[]; onMouseUp: () => void }) {
const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
const sync = useSync()
@ -361,7 +363,7 @@ function UserMessage(props: { message: UserMessage; parts: Part[]; onMouseDown:
onMouseOut={() => {
setHover(false)
}}
onMouseDown={props.onMouseDown}
onMouseUp={props.onMouseUp}
border={["left"]}
paddingTop={1}
paddingBottom={1}

View file

@ -99,7 +99,7 @@ function init() {
refocus()
},
replace(input: any, onClose?: () => void) {
focus = renderer.currentFocusedRenderable
if (store.stack.length === 0) focus = renderer.currentFocusedRenderable
for (const item of store.stack) {
if (item.onClose) item.onClose()
}