fix(tui): handle form clipboard shortcut (#43526)
This commit is contained in:
@@ -340,16 +340,36 @@ export function FormPrompt(props: {
|
||||
pick(row.value)
|
||||
}
|
||||
|
||||
function pasteCustom(value: string) {
|
||||
const current = answerField()
|
||||
if (!current || textual() || !custom() || confirm()) return false
|
||||
setStore("selected", rows().length)
|
||||
updateCustom(current, input() + value)
|
||||
setStore("editing", true)
|
||||
return true
|
||||
}
|
||||
|
||||
usePaste((event) => {
|
||||
if (keymap.mode.current() !== FORM_MODE) return
|
||||
const current = answerField()
|
||||
if (!current || textual() || !custom() || confirm()) return
|
||||
if (!pasteCustom(stripAnsiSequences(decodePasteBytes(event.bytes)).replace(/\r\n?/g, "\n"))) return
|
||||
event.preventDefault()
|
||||
setStore("selected", rows().length)
|
||||
updateCustom(current, input() + stripAnsiSequences(decodePasteBytes(event.bytes)).replace(/\r\n?/g, "\n"))
|
||||
setStore("editing", true)
|
||||
})
|
||||
|
||||
function pasteClipboard() {
|
||||
return clipboard
|
||||
.read()
|
||||
.then((content) => {
|
||||
if (content?.mime !== "text/plain") return
|
||||
const value = stripAnsiSequences(content.data).replace(/\r\n?/g, "\n")
|
||||
if (store.editing || textual()) {
|
||||
textarea?.insertText(value)
|
||||
return
|
||||
}
|
||||
pasteCustom(value)
|
||||
})
|
||||
.catch(toast.error)
|
||||
}
|
||||
|
||||
function commitInput(text: string) {
|
||||
const current = answerField()
|
||||
if (!current) return false
|
||||
@@ -505,6 +525,23 @@ export function FormPrompt(props: {
|
||||
|
||||
onMount(() => onCleanup(keymap.mode.push(FORM_MODE)))
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
mode: FORM_MODE,
|
||||
enabled: !confirm() && answerField() !== undefined,
|
||||
commands: [
|
||||
{
|
||||
id: "prompt.paste",
|
||||
title: "Paste from clipboard",
|
||||
group: "Form",
|
||||
run: (_input, event) => {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
return pasteClipboard()
|
||||
},
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
mode: FORM_MODE,
|
||||
priority: 1,
|
||||
|
||||
@@ -14,7 +14,13 @@ import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { createApi, createEventStream, createFetch } from "../../fixture/tui-client"
|
||||
|
||||
async function mountForm(root: string, width = 80, fields?: FormWithLocation["fields"], height = 20) {
|
||||
async function mountForm(
|
||||
root: string,
|
||||
width = 80,
|
||||
fields?: FormWithLocation["fields"],
|
||||
height = 20,
|
||||
clipboardText?: string,
|
||||
) {
|
||||
const state = path.join(root, "state")
|
||||
await mkdir(state, { recursive: true })
|
||||
|
||||
@@ -58,7 +64,7 @@ async function mountForm(root: string, width = 80, fields?: FormWithLocation["fi
|
||||
}}
|
||||
clipboard={{
|
||||
async read() {
|
||||
return undefined
|
||||
return clipboardText === undefined ? undefined : { data: clipboardText, mime: "text/plain" }
|
||||
},
|
||||
write(text) {
|
||||
copied.push(text)
|
||||
@@ -217,6 +223,34 @@ test("pasting on a custom choice opens its editor without submitting", async ()
|
||||
}
|
||||
})
|
||||
|
||||
test("clipboard shortcut opens a custom choice editor without submitting", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const prompt = await mountForm(
|
||||
tmp.path,
|
||||
80,
|
||||
[
|
||||
{
|
||||
key: "target",
|
||||
type: "string",
|
||||
options: [{ value: "staging", label: "Staging" }],
|
||||
custom: true,
|
||||
},
|
||||
],
|
||||
20,
|
||||
"production\nwest",
|
||||
)
|
||||
try {
|
||||
prompt.app.mockInput.pressKey("v", { ctrl: true })
|
||||
await prompt.app.waitFor(() => prompt.app.renderer.currentFocusedEditor?.plainText === "production\nwest")
|
||||
|
||||
await prompt.app.waitForFrame((frame) => frame.includes("production"))
|
||||
expect(prompt.app.captureCharFrame()).not.toContain("Type your own answer")
|
||||
expect(prompt.replies).toEqual([])
|
||||
} finally {
|
||||
prompt.app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("typing a custom multiselect answer selects it before commit", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const prompt = await mountForm(tmp.path, 80, [
|
||||
|
||||
Reference in New Issue
Block a user