Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- Fixed line numbers being selectable in Safari in the lightweight code highlighter. [#1037](https://github.com/sourcebot-dev/sourcebot/pull/1037)

### Added
- Added optional copy button to the lightweight code highlighter (`isCopyButtonVisible` prop), shown on hover. [#1037](https://github.com/sourcebot-dev/sourcebot/pull/1037)

## [4.16.1] - 2026-03-24

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Parser } from '@lezer/common'
import { LanguageDescription, StreamLanguage } from '@codemirror/language'
import { Highlighter, highlightTree } from '@lezer/highlight'
import { languages as builtinLanguages } from '@codemirror/language-data'
import { memo, useEffect, useMemo, useState } from 'react'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useCodeMirrorHighlighter } from '@/hooks/useCodeMirrorHighlighter'
import tailwind from '@/tailwind'
import { measure } from '@/lib/utils'
import { SourceRange } from '@/features/search'
import { CopyIconButton } from './copyIconButton'

// Define a plain text language
const plainTextLanguage = StreamLanguage.define({
Expand All @@ -25,6 +26,7 @@ interface LightweightCodeHighlighter {
/* 1-based line number offset */
lineNumbersOffset?: number;
renderWhitespace?: boolean;
isCopyButtonVisible?: boolean;
}

// The maximum number of characters per line that we will display in the preview.
Expand All @@ -46,6 +48,7 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
lineNumbers = false,
lineNumbersOffset = 1,
renderWhitespace = false,
isCopyButtonVisible = false,
} = props;

const unhighlightedLines = useMemo(() => {
Expand Down Expand Up @@ -110,6 +113,15 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
isFileTooLargeToDisplay,
]);

const onCopy = useCallback(() => {
try {
navigator.clipboard.writeText(code);
return true;
} catch {
return false;
}
}, [code]);

const lineCount = (highlightedLines ?? unhighlightedLines).length + lineNumbersOffset;
const lineNumberDigits = String(lineCount).length;
const lineNumberWidth = `${lineNumberDigits + 2}ch`; // +2 for padding
Expand All @@ -123,46 +135,55 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
}

return (
<div
style={{
fontFamily: tailwind.theme.fontFamily.editor,
fontSize: tailwind.theme.fontSize.editor,
whiteSpace: renderWhitespace ? 'pre-wrap' : 'none',
wordBreak: 'break-all',
}}
>
{(highlightedLines ?? unhighlightedLines).map((line, index) => (
<div
key={index}
className="flex"
>
{lineNumbers && (
<div className="relative group">
{isCopyButtonVisible && (
<CopyIconButton
onCopy={onCopy}
className="absolute top-1 right-1 z-10 opacity-0 group-hover:opacity-100 transition-opacity group-hover:bg-background"
/>
)}
<div
style={{
fontFamily: tailwind.theme.fontFamily.editor,
fontSize: tailwind.theme.fontSize.editor,
whiteSpace: renderWhitespace ? 'pre-wrap' : 'none',
wordBreak: 'break-all',
}}
>
{(highlightedLines ?? unhighlightedLines).map((line, index) => (
<div
key={index}
className="flex"
>
{lineNumbers && (
<span
style={{
width: lineNumberWidth,
minWidth: lineNumberWidth,
display: 'inline-block',
textAlign: 'left',
paddingLeft: '5px',
userSelect: 'none',
WebkitUserSelect: 'none',
fontFamily: tailwind.theme.fontFamily.editor,
color: tailwind.theme.colors.editor.gutterForeground,
}}
>
{index + lineNumbersOffset}
</span>
)}
<span
style={{
width: lineNumberWidth,
minWidth: lineNumberWidth,
display: 'inline-block',
textAlign: 'left',
paddingLeft: '5px',
userSelect: 'none',
fontFamily: tailwind.theme.fontFamily.editor,
color: tailwind.theme.colors.editor.gutterForeground,
flex: 1,
paddingLeft: '6px',
paddingRight: '2px',
}}
>
{index + lineNumbersOffset}
{line}
</span>
)}
<span
style={{
flex: 1,
paddingLeft: '6px',
paddingRight: '2px',
}}
>
{line}
</span>
</div>
))}
</div>
))}
</div>
</div>
)
})
Expand All @@ -184,7 +205,7 @@ async function getCodeParser(
return null;
}

if (!found.support) {
if (!found.support) {
await found.load();
}
return found.support ? found.support.language.parser : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const CodeBlock = ({
language={language}
lineNumbers={true}
renderWhitespace={true}
isCopyButtonVisible={true}
>
{code}
</LightweightCodeHighlighter>
Expand Down
Loading