tui: bind leader+i to image viewer
Default <leader>i opens the prompt image attachment viewer from the keyboard. Drop thumbnail captions so previews stay compact and fill their allotted height.
This commit is contained in:
@@ -640,6 +640,7 @@ export function Prompt(props: PromptProps) {
|
||||
"prompt.submit",
|
||||
"prompt.editor",
|
||||
"prompt.editor_context.clear",
|
||||
"prompt.images.view",
|
||||
"prompt.stash",
|
||||
"prompt.stash.pop",
|
||||
"prompt.stash.list",
|
||||
@@ -1510,7 +1511,7 @@ export function Prompt(props: PromptProps) {
|
||||
<Show when={config.prompt?.image_preview && imagePreviewsVisible()}>
|
||||
<box
|
||||
width="100%"
|
||||
height={imagePreviewHeight() + 2}
|
||||
height={imagePreviewHeight() + 1}
|
||||
flexDirection="row"
|
||||
flexShrink={0}
|
||||
justifyContent="flex-start"
|
||||
@@ -1523,23 +1524,28 @@ export function Prompt(props: PromptProps) {
|
||||
return (
|
||||
<box
|
||||
width={imagePreviewWidth()}
|
||||
height="100%"
|
||||
height={imagePreviewHeight()}
|
||||
flexBasis={imagePreviewWidth()}
|
||||
flexShrink={1}
|
||||
flexDirection="column"
|
||||
>
|
||||
<image
|
||||
id={`prompt-image-preview-${index()}`}
|
||||
source={file.uri}
|
||||
fit="cover"
|
||||
protocol="auto"
|
||||
width="100%"
|
||||
height={imagePreviewHeight()}
|
||||
onError={() => setFailed(true)}
|
||||
/>
|
||||
<text fg={theme.text.subdued} wrapMode="none" truncate>
|
||||
{failed() ? "No preview" : (file.mention?.text ?? `Image ${index() + 1}`)}
|
||||
</text>
|
||||
<Show
|
||||
when={!failed()}
|
||||
fallback={
|
||||
<box width="100%" height="100%" alignItems="center" justifyContent="center">
|
||||
<text fg={theme.text.subdued}>No preview</text>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
<image
|
||||
id={`prompt-image-preview-${index()}`}
|
||||
source={file.uri}
|
||||
fit="cover"
|
||||
protocol="auto"
|
||||
width="100%"
|
||||
height="100%"
|
||||
onError={() => setFailed(true)}
|
||||
/>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -164,6 +164,7 @@ export const Definitions = {
|
||||
|
||||
prompt_submit: keybind("none", "Submit prompt"),
|
||||
prompt_editor_context_clear: keybind("none", "Clear editor context"),
|
||||
prompt_images_view: keybind("<leader>i", "View image attachments"),
|
||||
prompt_skills: keybind("none", "Open skill selector"),
|
||||
prompt_stash: keybind("none", "Stash prompt"),
|
||||
prompt_stash_pop: keybind("none", "Pop stashed prompt"),
|
||||
@@ -364,6 +365,7 @@ export const CommandMap = {
|
||||
display_thinking: "session.toggle.thinking",
|
||||
prompt_submit: "prompt.submit",
|
||||
prompt_editor_context_clear: "prompt.editor_context.clear",
|
||||
prompt_images_view: "prompt.images.view",
|
||||
prompt_skills: "prompt.skills",
|
||||
prompt_stash: "prompt.stash",
|
||||
prompt_stash_pop: "prompt.stash.pop",
|
||||
|
||||
@@ -86,6 +86,14 @@ test("resolves a session move keybind", () => {
|
||||
expect(config.keybinds.get("session.move")).toMatchObject([{ key: "ctrl+o" }])
|
||||
})
|
||||
|
||||
test("resolves the image viewer keybind", () => {
|
||||
const defaults = resolve({}, { terminalSuspend: true })
|
||||
const overridden = resolve({ keybinds: { prompt_images_view: "ctrl+shift+i" } }, { terminalSuspend: true })
|
||||
|
||||
expect(defaults.keybinds.get("prompt.images.view")).toMatchObject([{ key: "<leader>i" }])
|
||||
expect(overridden.keybinds.get("prompt.images.view")).toMatchObject([{ key: "ctrl+shift+i" }])
|
||||
})
|
||||
|
||||
test("resolves message navigation defaults", () => {
|
||||
const config = resolve({}, { terminalSuspend: true })
|
||||
|
||||
|
||||
@@ -318,6 +318,9 @@ test("renders at most three left-aligned cropped thumbnails", async () => {
|
||||
expect(prompt.setup.renderer.root.findDescendantById("prompt-image-preview-3")).toBeUndefined()
|
||||
const frame = await prompt.setup.waitForFrame((frame) => frame.includes("+1 more"))
|
||||
expect(frame).toMatch(/^┃ █/m)
|
||||
expect(frame.match(/\[Image 1\]/g)).toHaveLength(1)
|
||||
expect(frame.match(/\[Image 2\]/g)).toHaveLength(1)
|
||||
expect(frame.match(/\[Image 3\]/g)).toHaveLength(1)
|
||||
|
||||
await prompt.setup.mockMouse.click(49, 1, MouseButtons.LEFT)
|
||||
await prompt.setup.waitForFrame((frame) => frame.includes("Image 4 of 4"))
|
||||
@@ -331,13 +334,20 @@ test("renders at most three left-aligned cropped thumbnails", async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("opens thumbnails by their exact mouse bounds and from the command palette", async () => {
|
||||
test("opens image attachments by keyboard, mouse, and command palette", async () => {
|
||||
const prompt = await mountPrompt(readPngClipboard, true)
|
||||
try {
|
||||
await pasteImages(prompt, 2)
|
||||
|
||||
const thumbnail = prompt.setup.renderer.root.findDescendantById("prompt-image-preview-1")
|
||||
if (!(thumbnail instanceof ImageRenderable)) throw new Error("Second image thumbnail did not render")
|
||||
|
||||
prompt.setup.mockInput.pressKey("x", { ctrl: true })
|
||||
prompt.setup.mockInput.pressKey("i")
|
||||
await prompt.setup.waitForFrame((frame) => frame.includes("Image 1 of 2"))
|
||||
prompt.setup.mockInput.pressCtrlC()
|
||||
await prompt.setup.waitForFrame((frame) => !frame.includes("Image 1 of 2"))
|
||||
|
||||
await prompt.setup.mockMouse.click(14, 1, MouseButtons.LEFT)
|
||||
|
||||
await prompt.setup.waitForFrame((frame) => frame.includes("Image 1 of 2"))
|
||||
@@ -369,7 +379,8 @@ test("opens thumbnails by their exact mouse bounds and from the command palette"
|
||||
prompt.setup.mockInput.pressKey("p", { ctrl: true })
|
||||
await prompt.setup.waitForFrame((frame) => frame.includes("Commands"))
|
||||
for (const key of "view image attachments") prompt.setup.mockInput.pressKey(key)
|
||||
await prompt.setup.waitForFrame((frame) => frame.includes("View image attachments"))
|
||||
const palette = await prompt.setup.waitForFrame((frame) => frame.includes("View image attachments"))
|
||||
expect(palette).toContain("ctrl+x i")
|
||||
prompt.setup.mockInput.pressEnter()
|
||||
await prompt.setup.waitForFrame((frame) => frame.includes("Image 1 of 2"))
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user