/** * Code Block Picker Extension * * Extract all code blocks from the conversation and show in a fuzzy picker. * Select one to copy to clipboard. * * Usage: * /codeblocks - Show picker with all code blocks * Ctrl+Shift+Y - Keyboard shortcut */ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { copyToClipboard, DynamicBorder } from "@mariozechner/pi-coding-agent"; import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; interface CodeBlock { language: string; code: string; preview: string; messageIndex: number; } // Extract code blocks from text function extractCodeBlocks(text: string, messageIndex: number): CodeBlock[] { const blocks: CodeBlock[] = []; // Match ```lang\ncode``` or ```\ncode``` const regex = /```(\w*)\n([\s\S]*?)```/g; let match: RegExpExecArray | null = regex.exec(text); while (match !== null) { const language = match[1] || "text"; const code = match[2].trim(); // Create preview: first line or first 50 chars const firstLine = code.split("\n")[0] || ""; const preview = firstLine.length > 50 ? `${firstLine.slice(0, 47)}...` : firstLine; blocks.push({ language, code, preview: preview || `(${language} block, ${code.split("\n").length} lines)`, messageIndex, }); match = regex.exec(text); } return blocks; } // Extract text content from message function getMessageText(message: unknown): string { if (!message) return ""; const msg = message as Record; // Handle string content if (typeof msg.content === "string") { return msg.content; } // Handle array content (TextContent[]) if (Array.isArray(msg.content)) { return msg.content .map((block) => { if (typeof block === "string") return block; if (block && typeof block === "object" && "text" in block) { return block.text; } return ""; }) .join("\n"); } return ""; } export default function codeBlockPickerExtension(pi: ExtensionAPI) { async function showCodeBlockPicker(ctx: ExtensionContext): Promise { // Get all session entries const entries = ctx.sessionManager.getBranch(); const blocks: CodeBlock[] = []; // Extract code blocks from message entries for (let i = 0; i < entries.length; i++) { const entry = entries[i]; if (entry && entry.type === "message") { const msgEntry = entry as { message?: unknown }; const text = getMessageText(msgEntry.message); const entryBlocks = extractCodeBlocks(text, i); blocks.push(...entryBlocks); } } if (blocks.length === 0) { ctx.ui.notify("No code blocks found in conversation", "info"); return; } // Build select items const items: SelectItem[] = blocks.map((block, index) => ({ value: String(index), label: `[${block.language}] ${block.preview}`, description: `${block.code.split("\n").length} lines`, })); // Show picker const result = await ctx.ui.custom((tui, theme, _kb, done) => { const container = new Container(); // Top border container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); // Title container.addChild( new Text(theme.fg("accent", theme.bold(`Code Blocks (${blocks.length})`)), 1, 0), ); container.addChild(new Text(theme.fg("dim", "Select to copy to clipboard"), 1, 0)); // SelectList const selectList = new SelectList(items, Math.min(items.length, 15), { selectedPrefix: (t) => theme.fg("accent", t), selectedText: (t) => theme.fg("accent", t), description: (t) => theme.fg("muted", t), scrollInfo: (t) => theme.fg("dim", t), noMatch: (t) => theme.fg("warning", t), }); selectList.onSelect = (item) => done(item.value); selectList.onCancel = () => done(null); container.addChild(selectList); // Help text container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter copy • esc cancel"), 1, 0)); // Bottom border container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); return { render: (w) => container.render(w), invalidate: () => container.invalidate(), handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); }, }; }); if (result !== null) { const blockIndex = parseInt(result, 10); const selectedBlock = blocks[blockIndex]; if (selectedBlock) { try { await copyToClipboard(selectedBlock.code); ctx.ui.notify( `Copied ${selectedBlock.language} block (${selectedBlock.code.split("\n").length} lines)`, "success", ); } catch (err) { ctx.ui.notify(`Failed to copy: ${err}`, "error"); } } } } // Register /codeblocks command pi.registerCommand("codeblocks", { description: "Pick a code block from conversation to copy", handler: async (_args, ctx) => { await showCodeBlockPicker(ctx); }, }); // Register keyboard shortcut (Ctrl+Shift+Y) pi.registerShortcut("ctrl+shift+y", { description: "Pick code block to copy", handler: async (ctx) => { await showCodeBlockPicker(ctx); }, }); }