Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chat-input-undo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Keep chat input undo and redo from changing files open in Markdown custom editors.
105 changes: 105 additions & 0 deletions packages/kilo-vscode/tests/prompt-undo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { expect, test, type Page } from "@playwright/test"

const GLOBALS = "colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern"

async function open(page: Page) {
await page.goto(`/iframe.html?id=prompt-input--default-420&viewMode=story&globals=${GLOBALS}`, { waitUntil: "load" })
const input = page.locator("textarea.prompt-input")
await expect(input).toBeVisible()
await page.evaluate(() => window.postMessage({ type: "connectionState", state: "connected" }, window.origin))
await expect(input).toBeEnabled()
return input
}

async function observe(page: Page, block = false) {
const trace = await page.evaluateHandle((block) => {
const events: KeyboardEvent[] = []
const forwarded: KeyboardEvent[] = []
window.addEventListener("keydown", (event) => {
if (event.key.length !== 1) return
forwarded.push(event)
if (block) event.preventDefault()
})
window.addEventListener(
"keydown",
(event) => {
if (event.key.length === 1) events.push(event)
},
true,
)
return { events, forwarded }
}, block)
return () =>
trace.evaluate((state) => ({
prevented: state.events.splice(0).map((event) => event.defaultPrevented),
forwarded: state.forwarded.splice(0).map((event) => event.key),
}))
}

test("native undo and redo stay local despite host key forwarding", async ({ page }) => {
const input = await open(page)
await input.pressSequentially("Draft text")
await expect(input).toHaveValue("Draft text")
const read = await observe(page, true)

await input.press("ControlOrMeta+z")
await expect(input).toHaveValue("")
expect(await read()).toEqual({ prevented: [false], forwarded: [] })

await input.press("ControlOrMeta+Shift+Z")
await expect(input).toHaveValue("Draft text")
expect(await read()).toEqual({ prevented: [false], forwarded: [] })
})

test("only supported history chords stop propagation without cancelling text defaults", async ({ page }) => {
const input = await open(page)
await input.pressSequentially("Draft text")
const read = await observe(page)

for (const modifier of ["Control", "Meta"]) {
for (const chord of ["z", "Shift+Z", "y"]) {
await input.press(`${modifier}+${chord}`)
expect(await read(), `${modifier}+${chord}`).toEqual({ prevented: [false], forwarded: [] })
}
for (const chord of ["c", "x", "v", "Alt+z", "Alt+Shift+Z", "Alt+y", "Shift+Y"]) {
await input.dispatchEvent("keydown", {
key: chord.split("+").at(-1),
ctrlKey: modifier === "Control",
metaKey: modifier === "Meta",
altKey: chord.includes("Alt"),
shiftKey: chord.includes("Shift"),
})
expect(await read(), `${modifier}+${chord}`).toEqual({
prevented: [false],
forwarded: [chord.split("+").at(-1)],
})
}
}
})

test("enhanced prompt undo restores the original without reaching the host", async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(window, "acquireVsCodeApi", {
value: () => ({
getState: () => undefined,
setState: () => undefined,
postMessage: (message: { type: string; requestId?: string }) => {
if (message.type !== "enhancePrompt") return
window.postMessage(
{ type: "enhancePromptResult", requestId: message.requestId, text: "Enhanced draft" },
window.origin,
)
},
}),
})
})
const input = await open(page)
await input.pressSequentially("Original draft")
await page.getByRole("button", { name: "Enhance prompt", exact: true }).click()
await expect(input).toHaveValue("Enhanced draft")
const read = await observe(page, true)

await input.press("ControlOrMeta+z")
await expect(input).toHaveValue("Original draft")
expect(await read()).toEqual({ prevented: [true], forwarded: [] })
})
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onInput={handleInput}
onKeyDown={(e) => {
if (speechDown(e)) return
const key = e.key.toLowerCase()
if ((e.ctrlKey || e.metaKey) && !e.altKey && (key === "z" || (key === "y" && !e.shiftKey))) {
e.stopPropagation()
}
handleKeyDown(e)
}}
onKeyUp={(e) => {
Expand Down
Loading