mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
ctr-g to clear images
This commit is contained in:
@@ -150,6 +150,15 @@ export default function TerminalChatInput({
|
||||
// even though the real TTY environment works fine. Mirroring the
|
||||
// behaviour for the raw data path keeps production logic untouched
|
||||
// while ensuring the unit tests observe the same outcome.
|
||||
// Ctrl+G (0x07) – clear only attached images, keep draft text intact.
|
||||
if (str === "\x07" && attachedImages.length > 0) {
|
||||
setAttachedImages([]);
|
||||
return; // prevent further handling
|
||||
}
|
||||
|
||||
// Ctrl+U (0x15) – traditional “clear line”. We allow Ink's TextInput
|
||||
// default behaviour to wipe the draft, but we ALSO clear attachments so
|
||||
// the two stay in sync.
|
||||
if (str === "\x15" && attachedImages.length > 0) {
|
||||
setAttachedImages([]);
|
||||
}
|
||||
@@ -198,7 +207,10 @@ export default function TerminalChatInput({
|
||||
}
|
||||
|
||||
// Slash command navigation: up/down to select, Tab to cycle, Enter to run.
|
||||
if (!confirmationPrompt && !loading && input.trim().startsWith("/")) {
|
||||
const trimmedSlash = input.trim();
|
||||
const isSlashCmd = /^[\/][a-zA-Z]+$/.test(trimmedSlash);
|
||||
|
||||
if (!confirmationPrompt && !loading && isSlashCmd) {
|
||||
const prefix = input.trim();
|
||||
const matches = SLASH_COMMANDS.filter((cmd: SlashCommand) =>
|
||||
cmd.command.startsWith(prefix),
|
||||
@@ -739,7 +751,7 @@ export default function TerminalChatInput({
|
||||
}
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1} marginBottom={1}>
|
||||
<Text color="gray">attached images (ctrl+u to clear):</Text>
|
||||
<Text color="gray">attached images (ctrl+g to clear):</Text>
|
||||
{attachedImages.map((p, i) => (
|
||||
<Text key={i} color="cyan">{`❯ ${path.basename(p)}`}</Text>
|
||||
))}
|
||||
@@ -769,7 +781,8 @@ export default function TerminalChatInput({
|
||||
showCursor
|
||||
value={input}
|
||||
onChange={(rawValue) => {
|
||||
let value = rawValue; // will be replaced after extraction
|
||||
// Strip any raw control-G char so it never shows up.
|
||||
let value = rawValue.replace(/\x07/g, "");
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Detect freshly-dropped image paths _while the user is
|
||||
@@ -778,7 +791,14 @@ export default function TerminalChatInput({
|
||||
|
||||
const { paths: newlyDropped, text: cleaned } = extractImagePaths(rawValue);
|
||||
|
||||
value = cleaned; // do not trim spaces – preserve exact typing
|
||||
value = cleaned;
|
||||
|
||||
// If the extraction removed everything (e.g., user only pasted
|
||||
// a file path followed by a space) we don’t want to leave a
|
||||
// dangling "/ " or other whitespace artefacts in the draft.
|
||||
if (value.trim().length === 0) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
if (newlyDropped.length > 0) {
|
||||
setAttachedImages((prev) => {
|
||||
@@ -816,7 +836,12 @@ export default function TerminalChatInput({
|
||||
)}
|
||||
</Box>
|
||||
{/* Slash command autocomplete suggestions */}
|
||||
{input.trim().startsWith("/") && (
|
||||
{(() => {
|
||||
const trimmed = input.trim();
|
||||
const showSlash =
|
||||
trimmed.startsWith("/") && /^[\/][a-zA-Z]+$/.test(trimmed);
|
||||
return showSlash;
|
||||
})() && (
|
||||
<Box flexDirection="column" paddingX={2} marginBottom={1}>
|
||||
{SLASH_COMMANDS.filter((cmd: SlashCommand) =>
|
||||
cmd.command.startsWith(input.trim()),
|
||||
|
||||
@@ -70,5 +70,11 @@ export function extractImagePaths(input: string): ExtractResult {
|
||||
return "";
|
||||
});
|
||||
|
||||
// Remove any leftover leading slash that was immediately followed by the
|
||||
// matched path (e.g. "/Users/foo.png → '/ '" after replacement). We only
|
||||
// strip it when it's followed by whitespace or end-of-string so normal
|
||||
// typing like "/help" is untouched.
|
||||
text = text.replace(/(^|\s)\/(?=\s|$)/g, "$1");
|
||||
|
||||
return { paths, text };
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ describe("Chat input attachment preview", () => {
|
||||
const frame1 = lastFrameStripped();
|
||||
expect(frame1.match(/foo\.png/g)?.length ?? 0).toBe(1);
|
||||
|
||||
await type(stdin, "\x15", flush); // Ctrl+U
|
||||
await type(stdin, "\x07", flush); // Ctrl+G (clear images only)
|
||||
|
||||
expect(lastFrameStripped()).not.toContain("foo.png");
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import { renderTui } from "./ui-test-helpers.js";
|
||||
// Mocks – keep in sync with other TerminalChatInput UI tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const createInputItemMock = vi.fn(async (_text: string, _imgs: Array<string>) => ({}));
|
||||
// mock without type annotations to avoid Vitest transform TS errors in JS test
|
||||
const createInputItemMock = vi.fn(async () => ({}));
|
||||
|
||||
vi.mock("../src/utils/input-utils.js", () => ({
|
||||
createInputItem: createInputItemMock,
|
||||
@@ -100,10 +101,36 @@ describe("Drag-and-drop image attachment", () => {
|
||||
|
||||
// createInputItem should have been called with the dropped image path
|
||||
expect(createInputItemMock).toHaveBeenCalled();
|
||||
const lastCall = createInputItemMock.mock.calls.at(-1);
|
||||
const calls = createInputItemMock.mock.calls;
|
||||
const lastCall = calls[calls.length - 1];
|
||||
expect(lastCall?.[1]).toEqual(["dropped.png"]);
|
||||
|
||||
cleanup();
|
||||
process.chdir(orig);
|
||||
});
|
||||
|
||||
it("does NOT show slash-command overlay for absolute paths", async () => {
|
||||
const orig = process.cwd();
|
||||
process.chdir(TMP);
|
||||
|
||||
const { stdin, flush, lastFrameStripped, cleanup } = renderTui(
|
||||
React.createElement(TerminalChatInput, props()),
|
||||
);
|
||||
|
||||
await flush();
|
||||
|
||||
// absolute path starting with '/'
|
||||
const absPath = path.join(TMP, "dropped.png");
|
||||
await type(stdin, `${absPath} `, flush);
|
||||
await flush();
|
||||
|
||||
const frame = lastFrameStripped();
|
||||
|
||||
// Should contain attachment preview but NOT typical slash-command suggestion like "/help"
|
||||
expect(frame).toContain("dropped.png");
|
||||
expect(frame).not.toContain("/help");
|
||||
|
||||
cleanup();
|
||||
process.chdir(orig);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user