1- import { VSCodeBadge , VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
1+ import { VSCodeBadge , VSCodeProgressRing , VSCodeButton } from "@vscode/webview-ui-toolkit/react"
22import deepEqual from "fast-deep-equal"
33import React , { memo , useCallback , useEffect , useMemo , useRef , useState , MouseEvent } from "react"
44
@@ -20,6 +20,62 @@ import { findMatchingResourceOrTemplate, getMcpServerDisplayName } from "@/utils
2020import { vscode } from "@/utils/vscode"
2121import { FileServiceClient } from "@/services/grpc-client"
2222import { CheckmarkControl } from "@/components/common/CheckmarkControl"
23+
24+ interface CopyButtonProps {
25+ textToCopy : string | undefined
26+ }
27+
28+ const CopyButtonStyled = styled ( VSCodeButton ) `
29+ position: absolute;
30+ bottom: 2px;
31+ right: 2px;
32+ z-index: 1;
33+ opacity: 0;
34+ `
35+
36+ interface WithCopyButtonProps {
37+ children : React . ReactNode
38+ textToCopy ?: string
39+ style ?: React . CSSProperties
40+ ref ?: React . Ref < HTMLDivElement >
41+ onMouseUp ?: ( event : MouseEvent < HTMLDivElement > ) => void
42+ }
43+
44+ const StyledContainer = styled . div `
45+ position: relative;
46+
47+ &:hover ${ CopyButtonStyled } {
48+ opacity: 1;
49+ }
50+ `
51+
52+ const WithCopyButton = React . forwardRef < HTMLDivElement , WithCopyButtonProps > (
53+ ( { children, textToCopy, style, onMouseUp, ...props } , ref ) => {
54+ const [ copied , setCopied ] = useState ( false )
55+
56+ const handleCopy = ( ) => {
57+ if ( ! textToCopy ) return
58+
59+ navigator . clipboard . writeText ( textToCopy ) . then ( ( ) => {
60+ setCopied ( true )
61+ setTimeout ( ( ) => {
62+ setCopied ( false )
63+ } , 1500 )
64+ } )
65+ }
66+
67+ return (
68+ < StyledContainer ref = { ref } onMouseUp = { onMouseUp } style = { style } { ...props } >
69+ { children }
70+ { textToCopy && (
71+ < CopyButtonStyled appearance = "icon" onClick = { handleCopy } aria-label = { copied ? "Copied" : "Copy" } >
72+ < span className = { `codicon codicon-${ copied ? "check" : "copy" } ` } > </ span >
73+ </ CopyButtonStyled >
74+ ) }
75+ </ StyledContainer >
76+ )
77+ } ,
78+ )
2379import { CheckpointControls , CheckpointOverlay } from "../common/CheckpointControls"
2480import CodeAccordian , { cleanPathPrefix } from "../common/CodeAccordian"
2581import CodeBlock , { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
@@ -90,6 +146,7 @@ const Markdown = memo(({ markdown }: { markdown?: string }) => {
90146 overflowWrap : "anywhere" ,
91147 marginBottom : - 15 ,
92148 marginTop : - 15 ,
149+ overflow : "hidden" , // contain child margins so that parent diff matches height of children
93150 } } >
94151 < MarkdownBlock markdown = { markdown } />
95152 </ div >
@@ -901,7 +958,7 @@ export const ChatRowContent = ({
901958 return < McpResponseDisplay responseText = { message . text || "" } />
902959 case "text" :
903960 return (
904- < div ref = { contentRef } onMouseUp = { handleMouseUp } style = { { position : "relative" } } >
961+ < WithCopyButton ref = { contentRef } onMouseUp = { handleMouseUp } textToCopy = { message . text } >
905962 < Markdown markdown = { message . text } />
906963 { quoteButtonState . visible && (
907964 < QuoteButton
@@ -912,7 +969,7 @@ export const ChatRowContent = ({
912969 } }
913970 />
914971 ) }
915- </ div >
972+ </ WithCopyButton >
916973 )
917974 case "reasoning" :
918975 return (
@@ -1132,13 +1189,13 @@ export const ChatRowContent = ({
11321189 } }
11331190 />
11341191 </ div >
1135- < div
1136- ref = { contentRef } // Added ref
1137- onMouseUp = { handleMouseUp } // Added handler
1192+ < WithCopyButton
1193+ ref = { contentRef }
1194+ onMouseUp = { handleMouseUp }
1195+ textToCopy = { text }
11381196 style = { {
11391197 color : "var(--vscode-charts-green)" ,
11401198 paddingTop : 10 ,
1141- position : "relative" , // Added position
11421199 } } >
11431200 < Markdown markdown = { text } />
11441201 { quoteButtonState . visible && (
@@ -1148,7 +1205,7 @@ export const ChatRowContent = ({
11481205 onClick = { handleQuoteClick }
11491206 />
11501207 ) }
1151- </ div >
1208+ </ WithCopyButton >
11521209 { message . partial !== true && hasChanges && (
11531210 < div style = { { paddingTop : 17 } } >
11541211 < SuccessButton
@@ -1295,13 +1352,13 @@ export const ChatRowContent = ({
12951352 } }
12961353 />
12971354 </ div >
1298- < div
1299- ref = { contentRef } // Added ref
1300- onMouseUp = { handleMouseUp } // Added handler
1355+ < WithCopyButton
1356+ ref = { contentRef }
1357+ onMouseUp = { handleMouseUp }
1358+ textToCopy = { text }
13011359 style = { {
13021360 color : "var(--vscode-charts-green)" ,
13031361 paddingTop : 10 ,
1304- position : "relative" , // Added position
13051362 } } >
13061363 < Markdown markdown = { text } />
13071364 { quoteButtonState . visible && (
@@ -1311,30 +1368,30 @@ export const ChatRowContent = ({
13111368 onClick = { handleQuoteClick }
13121369 />
13131370 ) }
1314- { message . partial !== true && hasChanges && (
1315- < div style = { { marginTop : 15 } } >
1316- < SuccessButton
1317- appearance = "secondary"
1318- disabled = { seeNewChangesDisabled }
1319- onClick = { ( ) => {
1320- setSeeNewChangesDisabled ( true )
1321- vscode . postMessage ( {
1322- type : "taskCompletionViewChanges" ,
1323- number : message . ts ,
1324- } )
1325- } } >
1326- < i
1327- className = "codicon codicon-new-file"
1328- style = { {
1329- marginRight : 6 ,
1330- cursor : seeNewChangesDisabled ? "wait" : "pointer" ,
1331- } }
1332- />
1333- See new changes
1334- </ SuccessButton >
1335- </ div >
1336- ) }
1337- </ div >
1371+ </ WithCopyButton >
1372+ { message . partial !== true && hasChanges && (
1373+ < div style = { { marginTop : 15 } } >
1374+ < SuccessButton
1375+ appearance = "secondary"
1376+ disabled = { seeNewChangesDisabled }
1377+ onClick = { ( ) => {
1378+ setSeeNewChangesDisabled ( true )
1379+ vscode . postMessage ( {
1380+ type : "taskCompletionViewChanges" ,
1381+ number : message . ts ,
1382+ } )
1383+ } } >
1384+ < i
1385+ className = "codicon codicon-new-file"
1386+ style = { {
1387+ marginRight : 6 ,
1388+ cursor : seeNewChangesDisabled ? "wait" : "pointer" ,
1389+ } }
1390+ />
1391+ See new changes
1392+ </ SuccessButton >
1393+ </ div >
1394+ ) }
13381395 </ div >
13391396 )
13401397 } else {
@@ -1362,7 +1419,11 @@ export const ChatRowContent = ({
13621419 { title }
13631420 </ div >
13641421 ) }
1365- < div ref = { contentRef } onMouseUp = { handleMouseUp } style = { { position : "relative" , paddingTop : 10 } } >
1422+ < WithCopyButton
1423+ ref = { contentRef }
1424+ onMouseUp = { handleMouseUp }
1425+ textToCopy = { question }
1426+ style = { { paddingTop : 10 } } >
13661427 < Markdown markdown = { question } />
13671428 < OptionsButtons
13681429 options = { options }
@@ -1379,7 +1440,7 @@ export const ChatRowContent = ({
13791440 } }
13801441 />
13811442 ) }
1382- </ div >
1443+ </ WithCopyButton >
13831444 </ >
13841445 )
13851446 case "new_task" :
@@ -1428,7 +1489,7 @@ export const ChatRowContent = ({
14281489 response = message . text
14291490 }
14301491 return (
1431- < div ref = { contentRef } onMouseUp = { handleMouseUp } style = { { position : "relative" } } >
1492+ < WithCopyButton ref = { contentRef } onMouseUp = { handleMouseUp } textToCopy = { response } >
14321493 < Markdown markdown = { response } />
14331494 < OptionsButtons
14341495 options = { options }
@@ -1445,7 +1506,7 @@ export const ChatRowContent = ({
14451506 } }
14461507 />
14471508 ) }
1448- </ div >
1509+ </ WithCopyButton >
14491510 )
14501511 }
14511512 default :
0 commit comments