Copy a code block of the last reply to the clipboard with one key

My personal dotfiles managed with YADM.
Tested on Ubuntu 25.10 and Ubuntu 25.04.
yadm clone <your-repo-url>
yadm config local.class work # or personal
yadm config yadm.alt-copy true
yadm alt
yadm bootstrap
SUPER + Return - TerminalSUPER + T - Terminal with tmuxSUPER + Space/D - Application launcherhooks/register.tsx 75 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, RenderSurface } from 'claude-code'
3
4import type { CodeBlock } from '../types'
5import { describeBlock } from '../core/codeblocks'
6import { findLastReplyBlocks } from './blocks'
7
8const PANE = 'code-yank'
9const HOTKEYS = '123456789abcdefghijklmnopqrstuvwxyz'
10const blocks = atom({ plugin: 'code-yank', key: 'blocks' } as const, [])
11
12export const register: Register = on => {
13 on('session.start', async ($, e, next) => {
14 await $.command.register({
15 name: 'yank',
16 description: 'Copy a code block of the last reply to the clipboard',
17 argumentHint: '[number]',
18 })
19
20 return next(e)
21 })
22
23 on('command.run', { command: 'yank' }, async ($, e) => {
24 const messages = await $.session.messages({})
25 if (!Array.isArray(messages)) return { text: `Cannot read the conversation: ${messages.deny}` }
26
27 const found = findLastReplyBlocks(messages)
28 if (found.length === 0) return { text: 'The last reply has no code blocks.' }
29
30 const chosen = e.args.trim() === '' ? undefined : found[Number.parseInt(e.args, 10) - 1]
31 if (e.args.trim() !== '' && chosen === undefined) return { text: `There is no code block ${e.args.trim()}. The last reply has ${found.length}.` }
32 if (chosen !== undefined) return { text: await copyBlock($, chosen) }
33 if (found.length === 1) return { text: await copyBlock($, found[0] as CodeBlock) }
34
35 await update($, blocks, () => found)
36
37 await $.ui.open({ id: PANE, title: 'Yank', focus: true, closeOnEscape: true, rows: Math.min(found.length, HOTKEYS.length) + 1 })
38
39 return { text: `${found.length} code blocks. Press a key to copy one.` }
40 })
41
42 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
43 const { Box, Button, Text } = $.ui.resolve(e)
44 const list = (await read($, blocks)).slice(0, HOTKEYS.length)
45 const width = Math.max(20, (e.props.bodyColumns ?? 80) - 6)
46
47 return (
48 <Box flexDirection="column">
49 {list.map((block, index) => (
50 <Box key={`row-${index}`}>
51 <Button
52 key={`copy-${index}`}
53 plain
54 hotkey={HOTKEYS[index]}
55 label={describeBlock(block).slice(0, width)}
56 autoFocus={index === 0 ? true : undefined}
57 onPress={async () => {
58 $.ui.toast(await copyBlock($, block, e.surface))
59 await $.ui.close({ id: PANE })
60 }}
61 />
62 </Box>
63 ))}
64 {list.length === 0 && <Text dimColor>No code blocks.</Text>}
65 </Box>
66 )
67 })
68}
69
70async function copyBlock($: EngineInterface, block: CodeBlock, surface?: RenderSurface): Promise<string> {
71 const copied = await $.ui.copy({ text: block.source, surface })
72
73 return copied.isCopied ? `Copied: ${describeBlock(block)}` : `Copy failed: ${copied.reason}`
74}
75core/codeblocks.ts 28 lines1import type { CodeBlock } from '../types'
2
3const FENCE = /^([ \t]*)(`{3,}|~{3,})([^\n`]*)\n([\s\S]*?)\n[ \t]*\2[ \t]*$/gm
4
5export function parseCodeBlocks(text: string): CodeBlock[] {
6 return [...text.matchAll(FENCE)].map(match => ({
7 language: (match[3] ?? '').trim().split(/\s+/)[0] ?? '',
8 source: stripIndent(match[4] ?? '', match[1] ?? ''),
9 }))
10}
11
12export function describeBlock(block: CodeBlock): string {
13 const firstLine = block.source.split('\n').find(line => line.trim() !== '')?.trim() ?? ''
14 const lineCount = block.source.split('\n').length
15 const language = block.language === '' ? 'text' : block.language
16
17 return `${language}, ${lineCount} ${lineCount === 1 ? 'line' : 'lines'}: ${firstLine}`
18}
19
20function stripIndent(source: string, indent: string): string {
21 if (indent === '') return source
22
23 return source
24 .split('\n')
25 .map(line => (line.startsWith(indent) ? line.slice(indent.length) : line))
26 .join('\n')
27}
28hooks/blocks.ts 21 lines1import type { SessionMessage } from 'claude-code'
2
3import type { CodeBlock } from '../types'
4import { parseCodeBlocks } from '../core/codeblocks'
5
6export function findLastReplyBlocks(messages: readonly SessionMessage[]): CodeBlock[] {
7 const blocks: CodeBlock[] = []
8
9 for (let index = messages.length - 1; index >= 0; index--) {
10 const message = messages[index]
11 if (message === undefined) continue
12 if (message.role === 'user' && message.text !== '' && (message.toolResults ?? []).length === 0) {
13 if (blocks.length > 0) break
14 continue
15 }
16 if (message.role === 'assistant') blocks.unshift(...parseCodeBlocks(message.text))
17 }
18
19 return blocks
20}
21types/index.d.ts 8 lines1export type CodeBlock = { language: string; source: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'code-yank': { blocks: CodeBlock[] }
6 }
7}
8