From 961de10110b7652026f13bb2b137f492e375ef6b Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 22 Sep 2025 17:51:25 -0400 Subject: [PATCH] sync --- bun.lock | 20 +-- packages/opencode/package.json | 4 +- .../src/cli/cmd/tui/component/prompt.tsx | 145 +++++++++++++----- 3 files changed, 117 insertions(+), 52 deletions(-) diff --git a/bun.lock b/bun.lock index c04379b54..72bfb72bd 100644 --- a/bun.lock +++ b/bun.lock @@ -143,8 +143,8 @@ "@openauthjs/openauth": "catalog:", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opentui/core": "0.1.24", - "@opentui/solid": "0.1.24", + "@opentui/core": "0.0.0-20250922-8d3a9252", + "@opentui/solid": "0.0.0-20250922-8d3a9252", "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", @@ -751,21 +751,21 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - "@opentui/core": ["@opentui/core@0.1.24", "", { "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.24", "@opentui/core-darwin-x64": "0.1.24", "@opentui/core-linux-arm64": "0.1.24", "@opentui/core-linux-x64": "0.1.24", "@opentui/core-win32-arm64": "0.1.24", "@opentui/core-win32-x64": "0.1.24", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" } }, "sha512-AbbrIvXxei9j9xfwQfe4TU+2P+x3MPIuaR1H0n01/jXmuCZGzl6Nl3xsSXC5iGTwaC6+RLsDnDBjNTENeIvetw=="], + "@opentui/core": ["@opentui/core@0.0.0-20250922-8d3a9252", "", { "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20250922-8d3a9252", "@opentui/core-darwin-x64": "0.0.0-20250922-8d3a9252", "@opentui/core-linux-arm64": "0.0.0-20250922-8d3a9252", "@opentui/core-linux-x64": "0.0.0-20250922-8d3a9252", "@opentui/core-win32-arm64": "0.0.0-20250922-8d3a9252", "@opentui/core-win32-x64": "0.0.0-20250922-8d3a9252", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" } }, "sha512-fHj/CSQZgwclIwGTqDdiw01N6iTWndQvUj+IuMxSXoiJQptwo/qGQjrnvM/QzC893KxEM8kEXIpk2Xgs0vcgRQ=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.24", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TLJkdIBeDdLA6SMAFGD9mi9ItcHa2eGqePt8B0DQr04M7kbe+Okqj7Y4Kuz2PJc5VJ+7MNowFwSjN1DPPjbpXw=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20250922-8d3a9252", "", { "os": "darwin", "cpu": "arm64" }, "sha512-D2Y42PGNCbzWHgTw3m++hYg5iPea76jpPjdSLuawZcvcIeygm2OYa20MbuYAsirKjEhKUacGsl4KFpVNmXnbmw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.24", "", { "os": "darwin", "cpu": "x64" }, "sha512-qRW0HGdCbETIw/CHQ6jevcP/+ovi+kttOOgJW0Cr2OOAltOLfi/4jkPNAwQL3H2UslGEOGGYgbbpYFKCupUZ5Q=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20250922-8d3a9252", "", { "os": "darwin", "cpu": "x64" }, "sha512-2QhY7psDp4HfkEGYyRfRfRfswLy2GTNraLtujlV88P1276OWmWtb46DgB9LOuuAUnh+OVpdTZmhAXdJFfwQpbQ=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.24", "", { "os": "linux", "cpu": "arm64" }, "sha512-u2fvRvOyJfkMYLPNM8X+MxPqEqHCj2VyzhaIdtON72uIfXx0MvIi/srBuFe/lxnIw6ynnZUKs0A+UR71fhQqZA=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20250922-8d3a9252", "", { "os": "linux", "cpu": "arm64" }, "sha512-hy2VxwYqWrTuzvVhcelbVFD+lrzd6PkNN34jPpjKcjcMI7sUaQNQGxxFLQqhF4o4kv3lyjQF+foWvSB2Iz+UzQ=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.24", "", { "os": "linux", "cpu": "x64" }, "sha512-hzJDdAtPgLS716qXAVT7Rlci+VegPhoy4fhBcoZQ0HH1eV/olRWuLn8uonARnS59uq4cSxamhMOYm98tFCL7zg=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20250922-8d3a9252", "", { "os": "linux", "cpu": "x64" }, "sha512-E1xpZLwx7Gm2LvzmyZyeBoUtMXiYonzliDBl7V/awIRZ3wBSXAae1XrvAu0vOcjPVe0VsREbwA55SSETEb4gWQ=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.24", "", { "os": "win32", "cpu": "arm64" }, "sha512-YzraZ5kEp+DhXuUYP//KGCbWxlEc3Zcar2swggE0ktvqIgtfo0mOQddSkbWFEoQVcVeitCTbNY023G72fe+LUg=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20250922-8d3a9252", "", { "os": "win32", "cpu": "arm64" }, "sha512-nH+UGk01zu2djfDMzOnEnlcobhv1XJD9I2MiAvZcADwzjKQvytLm9fAwyw4d9xvkuH7gDkdYk1pkWnD+eogHcw=="], "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.24", "", { "os": "win32", "cpu": "x64" }, "sha512-IuP5j6qYiSmdCc4mDxOZqDCRjXMGwM1dYk2PVn8AlIuwsG4qZLwEi7OrupBVre3HB2/ppyodeyw33l22nImMzA=="], - "@opentui/solid": ["@opentui/solid@0.1.24", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.24", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-eBNINWYrbuM4W0W0txmVuJuoMmVw4mySDvN1MmAB068BXllrSY1e3U7NI33+xIPGTE/ShasqCPeCVyzQWF73yw=="], + "@opentui/solid": ["@opentui/solid@0.0.0-20250922-8d3a9252", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20250922-8d3a9252", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-SBQuViEyiDKrXC2mfhqQwAq+ZX1fo9YWAWId6K1e0ueTTv3HO3dsTg2vhKmlo66zAojd4mVJmWBSg1+6B3ei4A=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -3117,6 +3117,8 @@ "@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="], + "@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20250922-8d3a9252", "", { "os": "win32", "cpu": "x64" }, "sha512-MDdcP3xuOomY2CEdzJIrV6HYreq6bYAxPWyOdUX48NjMIRKhdg/Y4W2xLGyMHr8j0MczLt/qg96dIJ9y+N4YBA=="], + "@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d25303df6..e62dd4c89 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -36,8 +36,8 @@ "@openauthjs/openauth": "catalog:", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opentui/core": "0.1.24", - "@opentui/solid": "0.1.24", + "@opentui/core": "0.0.0-20250922-8d3a9252", + "@opentui/solid": "0.0.0-20250922-8d3a9252", "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt.tsx index e9f8daeac..8c1ea1e69 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt.tsx @@ -1,5 +1,5 @@ import { InputRenderable, TextAttributes, BoxRenderable, type ParsedKey } from "@opentui/core" -import { createEffect, createMemo, createResource, For, Match, onMount, Switch } from "solid-js" +import { createEffect, createMemo, createResource, For, Match, onMount, Show, Switch } from "solid-js" import { useLocal } from "../context/local" import { Theme } from "../context/theme" @@ -75,6 +75,9 @@ export function Prompt(props: PromptProps) { { let diff = value.length - store.input.length setStore( @@ -198,7 +201,14 @@ export function Prompt(props: PromptProps) { type AutocompleteRef = { onInput: (value: string) => void onKeyDown: (e: ParsedKey) => void - visible: boolean + visible: false | "@" | "/" +} + +type AutocompleteOption = { + type: string + value: string + display: string + description?: string } function Autocomplete(props: { @@ -212,7 +222,7 @@ function Autocomplete(props: { const [store, setStore] = createStore({ index: 0, selected: 0, - visible: false, + visible: false as AutocompleteRef["visible"], position: { x: 0, y: 0, width: 0 }, }) const filter = createMemo(() => { @@ -237,10 +247,32 @@ function Autocomplete(props: { }, ) + const commands: { + name: string + description: string + }[] = {} + const options = createMemo(() => { - const mixed = [...files().map((x) => ({ type: "file", value: x }))] + const mixed: AutocompleteOption[] = + store.visible === "@" + ? [...files().map((x) => ({ type: "file", value: x, display: x }))] + : [ + { + type: "command", + value: "foo", + description: "This is a foo command", + display: "/foo".padEnd(10), + }, + { + type: "command", + value: "model", + description: "This is a bar command", + display: "/model".padEnd(10), + }, + ] + if (!filter()) return mixed const result = fuzzysort.go(filter(), mixed, { - keys: ["value"], + keys: ["display"], }) return result.map((arr) => arr.obj) }) @@ -253,14 +285,48 @@ function Autocomplete(props: { function move(direction: -1 | 1) { if (!store.visible) return let next = store.selected + direction - if (next < 0) next = files().length - 1 - if (next >= files().length) next = 0 + if (next < 0) next = options().length - 1 + if (next >= options().length) next = 0 setStore("selected", next) } - function show() { + function select() { + const selected = options()[store.selected] + if (!selected) return + + if (selected.type === "file") { + const part: Prompt["parts"][number] = { + type: "file", + mime: "text/plain", + filename: selected.value, + url: `file://${process.cwd()}/${selected.value}`, + source: { + type: "file", + text: { + start: store.index, + end: store.index + selected.value.length + 1, + value: "@" + selected, + }, + path: selected.value, + }, + } + props.setPrompt((draft) => { + const append = "@" + selected.value + " " + if (store.index === 0) draft.input = append + if (store.index > 0) draft.input = draft.input.slice(0, store.index) + append + draft.parts.push(part) + }) + } + + if (selected.type === "command") { + } + + setTimeout(() => hide(), 0) + } + + function show(mode: "@" | "/") { setStore({ - visible: true, + visible: mode, index: props.input().cursorPosition, position: { x: props.anchor().x, @@ -271,6 +337,7 @@ function Autocomplete(props: { } function hide() { + if (store.visible === "/") props.input().value = "" setStore("visible", false) } @@ -287,61 +354,57 @@ function Autocomplete(props: { if (e.name === "up") move(-1) if (e.name === "down") move(1) if (e.name === "escape") hide() - if (e.name === "return") { - const file = files()[store.selected] - if (!file) return - const part: Prompt["parts"][number] = { - type: "file", - mime: "text/plain", - filename: file, - url: `file://${process.cwd()}/${file}`, - source: { - type: "file", - text: { - start: store.index, - end: store.index + file.length + 1, - value: "@" + file, - }, - path: file, - }, - } - props.setPrompt((draft) => { - const append = "@" + file + " " - if (store.index === 0) draft.input = append - if (store.index > 0) draft.input = draft.input.slice(0, store.index) + append - draft.parts.push(part) - }) - setTimeout(() => hide(), 0) - } + if (e.name === "return") select() } if (!store.visible && e.name === "@") { const last = props.value.at(-1) if (last === " " || last === undefined) { - show() + show("@") } } + + if (!store.visible && e.name === "/") { + if (props.input().cursorPosition === 0) show("/") + } }, }) }) + + const height = createMemo(() => { + if (options().length) return Math.min(10, options().length) + return 1 + }) + return ( - - + + + No matching items + + } + > {(option, index) => ( - {option.value} + {option.display} + + {option.description} + )}