@@ -2,11 +2,12 @@ import { Parser } from '@lezer/common'
22import { LanguageDescription , StreamLanguage } from '@codemirror/language'
33import { Highlighter , highlightTree } from '@lezer/highlight'
44import { languages as builtinLanguages } from '@codemirror/language-data'
5- import { memo , useEffect , useMemo , useState } from 'react'
5+ import { memo , useCallback , useEffect , useMemo , useState } from 'react'
66import { useCodeMirrorHighlighter } from '@/hooks/useCodeMirrorHighlighter'
77import tailwind from '@/tailwind'
88import { measure } from '@/lib/utils'
99import { SourceRange } from '@/features/search'
10+ import { CopyIconButton } from './copyIconButton'
1011
1112// Define a plain text language
1213const plainTextLanguage = StreamLanguage . define ( {
@@ -25,6 +26,7 @@ interface LightweightCodeHighlighter {
2526 /* 1-based line number offset */
2627 lineNumbersOffset ?: number ;
2728 renderWhitespace ?: boolean ;
29+ isCopyButtonVisible ?: boolean ;
2830}
2931
3032// The maximum number of characters per line that we will display in the preview.
@@ -46,6 +48,7 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
4648 lineNumbers = false ,
4749 lineNumbersOffset = 1 ,
4850 renderWhitespace = false ,
51+ isCopyButtonVisible = false ,
4952 } = props ;
5053
5154 const unhighlightedLines = useMemo ( ( ) => {
@@ -110,6 +113,15 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
110113 isFileTooLargeToDisplay ,
111114 ] ) ;
112115
116+ const onCopy = useCallback ( ( ) => {
117+ try {
118+ navigator . clipboard . writeText ( code ) ;
119+ return true ;
120+ } catch {
121+ return false ;
122+ }
123+ } , [ code ] ) ;
124+
113125 const lineCount = ( highlightedLines ?? unhighlightedLines ) . length + lineNumbersOffset ;
114126 const lineNumberDigits = String ( lineCount ) . length ;
115127 const lineNumberWidth = `${ lineNumberDigits + 2 } ch` ; // +2 for padding
@@ -123,47 +135,55 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
123135 }
124136
125137 return (
126- < div
127- style = { {
128- fontFamily : tailwind . theme . fontFamily . editor ,
129- fontSize : tailwind . theme . fontSize . editor ,
130- whiteSpace : renderWhitespace ? 'pre-wrap' : 'none' ,
131- wordBreak : 'break-all' ,
132- } }
133- >
134- { ( highlightedLines ?? unhighlightedLines ) . map ( ( line , index ) => (
135- < div
136- key = { index }
137- className = "flex"
138- >
139- { lineNumbers && (
138+ < div className = "relative group" >
139+ { isCopyButtonVisible && (
140+ < CopyIconButton
141+ onCopy = { onCopy }
142+ className = "absolute top-1 right-1 z-10 opacity-0 group-hover:opacity-100 transition-opacity group-hover:bg-background"
143+ />
144+ ) }
145+ < div
146+ style = { {
147+ fontFamily : tailwind . theme . fontFamily . editor ,
148+ fontSize : tailwind . theme . fontSize . editor ,
149+ whiteSpace : renderWhitespace ? 'pre-wrap' : 'none' ,
150+ wordBreak : 'break-all' ,
151+ } }
152+ >
153+ { ( highlightedLines ?? unhighlightedLines ) . map ( ( line , index ) => (
154+ < div
155+ key = { index }
156+ className = "flex"
157+ >
158+ { lineNumbers && (
159+ < span
160+ style = { {
161+ width : lineNumberWidth ,
162+ minWidth : lineNumberWidth ,
163+ display : 'inline-block' ,
164+ textAlign : 'left' ,
165+ paddingLeft : '5px' ,
166+ userSelect : 'none' ,
167+ WebkitUserSelect : 'none' ,
168+ fontFamily : tailwind . theme . fontFamily . editor ,
169+ color : tailwind . theme . colors . editor . gutterForeground ,
170+ } }
171+ >
172+ { index + lineNumbersOffset }
173+ </ span >
174+ ) }
140175 < span
141176 style = { {
142- width : lineNumberWidth ,
143- minWidth : lineNumberWidth ,
144- display : 'inline-block' ,
145- textAlign : 'left' ,
146- paddingLeft : '5px' ,
147- userSelect : 'none' ,
148- WebkitUserSelect : 'none' ,
149- fontFamily : tailwind . theme . fontFamily . editor ,
150- color : tailwind . theme . colors . editor . gutterForeground ,
177+ flex : 1 ,
178+ paddingLeft : '6px' ,
179+ paddingRight : '2px' ,
151180 } }
152181 >
153- { index + lineNumbersOffset }
182+ { line }
154183 </ span >
155- ) }
156- < span
157- style = { {
158- flex : 1 ,
159- paddingLeft : '6px' ,
160- paddingRight : '2px' ,
161- } }
162- >
163- { line }
164- </ span >
165- </ div >
166- ) ) }
184+ </ div >
185+ ) ) }
186+ </ div >
167187 </ div >
168188 )
169189} )
@@ -185,7 +205,7 @@ async function getCodeParser(
185205 return null ;
186206 }
187207
188- if ( ! found . support ) {
208+ if ( ! found . support ) {
189209 await found . load ( ) ;
190210 }
191211 return found . support ? found . support . language . parser : null ;
0 commit comments