Compare commits

..

1 Commits

Author SHA1 Message Date
Dax Raad 2ada79018e feat(plugin): register command callbacks 2026-08-19 18:19:03 -04:00
11 changed files with 31 additions and 64 deletions
@@ -1,6 +1,5 @@
import { useFile } from "@/context/file"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { PROMPT_FILE_DRAG_TYPE } from "@opencode-ai/session-ui/v2/prompt-input/drag"
import "@opencode-ai/ui/v2/file-tree-v2.css"
import {
createEffect,
@@ -93,7 +92,6 @@ const FileTreeNodeV2 = (
onDragStart={(event: DragEvent) => {
if (!local.draggable) return
event.dataTransfer?.setData("text/plain", `file:${local.node.path}`)
event.dataTransfer?.setData(PROMPT_FILE_DRAG_TYPE, local.node.path)
event.dataTransfer?.setData("text/uri-list", pathToFileUrl(local.node.path))
if (event.dataTransfer) event.dataTransfer.effectAllowed = "copy"
withFileDragImage(event)
@@ -3,7 +3,6 @@ import { encodeFilePath } from "@/context/file/path"
import { Collapsible } from "@opencode-ai/ui/collapsible"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon"
import { PROMPT_FILE_DRAG_TYPE } from "@opencode-ai/session-ui/v2/prompt-input/drag"
import {
createEffect,
createMemo,
@@ -158,7 +157,6 @@ const FileTreeNode = (
onDragStart={(event: DragEvent) => {
if (!local.draggable) return
event.dataTransfer?.setData("text/plain", `file:${local.node.path}`)
event.dataTransfer?.setData(PROMPT_FILE_DRAG_TYPE, local.node.path)
event.dataTransfer?.setData("text/uri-list", pathToFileUrl(local.node.path))
if (event.dataTransfer) event.dataTransfer.effectAllowed = "copy"
withFileDragImage(event)
@@ -1,6 +1,5 @@
import { onMount } from "solid-js"
import { makeEventListener } from "@solid-primitives/event-listener"
import { isPromptAttachmentDrag } from "@opencode-ai/session-ui/v2/prompt-input/drag"
import { showToast } from "@/utils/toast"
import { type ContentPart, type ImageAttachmentPart, type usePrompt } from "@/context/prompt"
import { useLanguage } from "@/context/language"
@@ -165,13 +164,13 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
const handleGlobalDragOver = (event: DragEvent) => {
if (input.isDialogActive()) return
if (!isPromptAttachmentDrag(event)) return
event.preventDefault()
const hasFiles = event.dataTransfer?.types.includes("Files")
const hasText = event.dataTransfer?.types.includes("text/plain")
if (hasFiles) {
input.setDraggingType("image")
} else {
} else if (hasText) {
input.setDraggingType("@mention")
}
}
@@ -185,7 +184,6 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
const handleGlobalDrop = async (event: DragEvent) => {
if (input.isDialogActive()) return
if (!isPromptAttachmentDrag(event)) return
event.preventDefault()
input.setDraggingType(null)
+14 -11
View File
@@ -1,16 +1,19 @@
import type { CommandApi } from "@opencode-ai/client/effect/api"
import type { CommandInfo } from "@opencode-ai/client"
import type { Effect } from "effect"
import type { Transform } from "./registration.js"
import type { Session } from "@opencode-ai/schema/session"
import type { Effect, Scope } from "effect"
import type { Registration } from "./registration.js"
export interface CommandDraft {
list(): readonly CommandInfo[]
get(name: string): CommandInfo | undefined
update(name: string, update: (command: CommandInfo) => void): void
remove(name: string): void
export interface CommandInvocation {
readonly sessionID: Session.ID
readonly arguments: string
}
export interface CommandDomain extends CommandApi<unknown> {
readonly transform: Transform<CommandDraft>
readonly reload: () => Effect.Effect<void>
export interface CommandDefinition {
readonly name: string
readonly description?: string
readonly run: (input: CommandInvocation) => Effect.Effect<void>
}
export interface CommandDomain extends Pick<CommandApi<unknown>, "list"> {
readonly register: (definition: CommandDefinition) => Effect.Effect<Registration, never, Scope.Scope>
}
+13 -10
View File
@@ -1,15 +1,18 @@
import type { CommandApi } from "@opencode-ai/client/promise/api"
import type { CommandInfo } from "@opencode-ai/client"
import type { Transform } from "./registration.js"
import type { Session } from "@opencode-ai/schema/session"
import type { Registration } from "./registration.js"
export interface CommandDraft {
list(): readonly CommandInfo[]
get(name: string): CommandInfo | undefined
update(name: string, update: (command: CommandInfo) => void): void
remove(name: string): void
export interface CommandInvocation {
readonly sessionID: Session.ID
readonly arguments: string
}
export interface CommandDomain extends CommandApi {
readonly transform: Transform<CommandDraft>
readonly reload: () => Promise<void>
export interface CommandDefinition {
readonly name: string
readonly description?: string
readonly run: (input: CommandInvocation) => Promise<void>
}
export interface CommandDomain extends Pick<CommandApi, "list"> {
readonly register: (definition: CommandDefinition) => Promise<Registration>
}
-1
View File
@@ -20,7 +20,6 @@
"./v2/*.css": "./src/v2/components/*.css",
"./v2/*": "./src/v2/components/*.tsx",
"./v2/prompt-input": "./src/v2/components/prompt-input/index.tsx",
"./v2/prompt-input/drag": "./src/v2/components/prompt-input/drag.ts",
"./v2/prompt-input/interaction": "./src/v2/components/prompt-input/interaction.ts",
"./v2/prompt-input/store": "./src/v2/components/prompt-input/store.ts",
"./v2/prompt-input/types": "./src/v2/components/prompt-input/types.ts"
@@ -1,7 +1,6 @@
import { onMount } from "solid-js"
import { makeEventListener } from "@solid-primitives/event-listener"
import type { PromptInputV2Attachment, PromptInputV2Prompt } from "./types"
import { isPromptAttachmentDrag } from "./drag"
const accepted = [
"image/png",
@@ -178,7 +177,6 @@ export function createPromptInputV2Attachments(
}
const handleDrop = async (event: DragEvent) => {
if (input.isDialogActive()) return
if (!isPromptAttachmentDrag(event)) return
event.preventDefault()
input.setDraggingType(null)
const plainText = event.dataTransfer?.getData("text/plain")
@@ -195,10 +193,9 @@ export function createPromptInputV2Attachments(
onMount(() => {
makeEventListener(document, "dragover", (event) => {
if (input.isDialogActive()) return
if (!isPromptAttachmentDrag(event)) return
event.preventDefault()
if (event.dataTransfer?.types.includes("Files")) input.setDraggingType("image")
else input.setDraggingType("@mention")
else if (event.dataTransfer?.types.includes("text/plain")) input.setDraggingType("@mention")
})
makeEventListener(document, "dragleave", (event) => {
if (!input.isDialogActive() && !event.relatedTarget) input.setDraggingType(null)
@@ -1,18 +0,0 @@
import { describe, expect, test } from "bun:test"
import { isPromptAttachmentDrag, PROMPT_FILE_DRAG_TYPE } from "./drag"
describe("prompt attachment drag", () => {
test("ignores ordinary text and link drags", () => {
expect(drag(["text/plain"])).toBe(false)
expect(drag(["text/plain", "text/uri-list"])).toBe(false)
})
test("accepts files and OpenCode file tree entries", () => {
expect(drag(["Files"])).toBe(true)
expect(drag(["text/plain", PROMPT_FILE_DRAG_TYPE])).toBe(true)
})
})
function drag(types: string[]) {
return isPromptAttachmentDrag({ dataTransfer: { types } as unknown as DataTransfer })
}
@@ -1,7 +0,0 @@
export const PROMPT_FILE_DRAG_TYPE = "application/x-opencode-file"
export function isPromptAttachmentDrag(event: Pick<DragEvent, "dataTransfer">) {
const types = event.dataTransfer?.types
if (!types) return false
return types.includes("Files") || types.includes(PROMPT_FILE_DRAG_TYPE)
}
@@ -114,7 +114,7 @@ export function PromptInputV2(props: PromptInputV2Props) {
class="group/prompt-input relative min-h-[96px] w-full overflow-clip rounded-xl bg-v2-background-bg-base"
classList={{
"shadow-[var(--v2-elevation-raised)]": !props.borderUnderlay,
"outline outline-1 outline-v2-icon-icon-info outline-dashed": state.drag === "active",
"border border-v2-icon-icon-info border-dashed": state.drag === "active",
}}
onSubmit={(event) => {
event.preventDefault()
@@ -2,7 +2,6 @@ import { createEffect, on, type Accessor } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
import { useFilteredList } from "@opencode-ai/ui/hooks"
import { createPromptInputV2Attachments, type PromptInputV2AttachmentConfig } from "./attachments"
import { isPromptAttachmentDrag } from "./drag"
import { createPromptInputV2Store, type PromptInputV2StoreInput } from "./store"
import type {
PromptInputV2Attachment,
@@ -401,19 +400,16 @@ export function createPromptInputV2Controller(input: {
target.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertFromPaste", data: text }))
},
onDragEnter(event: DragEvent) {
if (attachments && !isPromptAttachmentDrag(event)) return
event.preventDefault()
dispatch({ type: "drag.enter" })
},
onDragOver(event: DragEvent) {
if (attachments && !isPromptAttachmentDrag(event)) return
event.preventDefault()
},
onDragLeave() {
dispatch({ type: "drag.leave" })
},
onDrop(event: DragEvent) {
if (attachments && !isPromptAttachmentDrag(event)) return
event.preventDefault()
dispatch({ type: "drag.leave" })
if (attachments) {