Core & UI: Support line numbers in codeblock

This commit is contained in:
Fuma Nama
2025-05-12 00:21:20 +09:00
parent c7eb450dc6
commit 3372792d47
7 changed files with 80 additions and 24 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'fumadocs-twoslash': patch
'fumadocs-core': patch
'fumadocs-ui': patch
---
Support line numbers in codeblock
+2 -2
View File
@@ -218,7 +218,7 @@ console.log('Hello World');
We support some of the [Shiki Transformers](https://shiki.style/packages/transformers), allowing you to highlight/style specific lines.
````md tab="Input"
````md tab="Input" lineNumbers
```tsx
// highlight a line
<div>Hello World</div> // [\!code highlight]
@@ -233,7 +233,7 @@ console.log('hello'); // [\!code ++]
```
````
```tsx tab="Output"
```tsx tab="Output" lineNumbers
// highlight a line
<div>Hello World</div> // [!code highlight]
@@ -83,7 +83,7 @@ console.log('Hello World');
Learn more about [Twoslash notations](https://twoslash.netlify.app/refs/notations).
```ts twoslash title="Test"
```ts twoslash title="Test" lineNumbers
type Player = {
/**
* The player name
+26 -7
View File
@@ -17,10 +17,17 @@ import type { CodeBlockIcon, IconOptions } from './transformer-icon';
import { transformerIcon } from './transformer-icon';
import { defaultThemes, getHighlighter } from '@/highlight/shiki';
interface MetaValue {
name: string;
regex: RegExp;
}
type Meta = Record<string, unknown>;
type MetaValue =
| {
name: string;
regex: RegExp;
}
| {
regex: RegExp;
onSet: (map: Meta, match: string[]) => void;
};
/**
* Custom meta string values
@@ -38,6 +45,14 @@ const metaValues: MetaValue[] = [
name: 'tab',
regex: /tab="(?<value>[^"]+)"/,
},
{
regex: /lineNumbers=(\d+)|lineNumbers/,
onSet(map, args) {
map['data-line-numbers'] = true;
if (args[0] !== undefined)
map['data-line-numbers-start'] = Number(args[0]);
},
},
];
export const rehypeCodeDefaultOptions: RehypeCodeOptions = {
@@ -58,12 +73,16 @@ export const rehypeCodeDefaultOptions: RehypeCodeOptions = {
}),
],
parseMetaString(meta) {
const map: Record<string, string> = {};
const map: Meta = {};
for (const value of metaValues) {
meta = meta.replace(value.regex, (_, ...args) => {
const first = args.at(0);
map[value.name] = typeof first === 'string' ? first : '';
if ('onSet' in value) {
value.onSet(map, args);
} else {
const first = args.at(0);
map[value.name] = typeof first === 'string' ? first : '';
}
return '';
});
-4
View File
@@ -10,10 +10,6 @@
--color-twoslash-tag-annotate: #1ba67320;
}
.twoslash .line {
display: inline-flex;
}
.twoslash-meta-line,
.twoslash-completion-list {
position: relative;
+20 -7
View File
@@ -3,9 +3,26 @@
color: var(--shiki-light);
}
code .line {
min-height: 1em;
@apply relative inline-block;
}
&[data-line-numbers] code .line {
width: calc(100% - 6 * var(--spacing));
counter-increment: line;
@apply ml-6;
&::after {
position: absolute;
content: counter(line);
@apply top-0 -left-4 text-fd-muted-foreground/60;
}
}
code .diff::before {
position: absolute;
left: 6px;
left: var(--spacing);
}
code .diff.remove {
@@ -28,15 +45,11 @@
}
code .diff {
width: calc(100% + 8 * var(--spacing));
@apply relative inline-block -mx-4 px-4;
@apply relative;
}
.highlighted {
width: calc(100% + 8 * var(--spacing));
@apply inline-block -mx-4 px-4 bg-fd-primary/10;
@apply bg-fd-primary/10;
}
.highlighted-word {
+24 -3
View File
@@ -40,6 +40,16 @@ export type CodeBlockProps = HTMLAttributes<HTMLElement> & {
keepBackground?: boolean;
viewportProps?: ScrollAreaViewportProps;
/**
* show line numbers
*/
'data-line-numbers'?: boolean;
/**
* @defaultValue 1
*/
'data-line-numbers-start'?: number;
};
export const Pre = forwardRef<HTMLPreElement, HTMLAttributes<HTMLPreElement>>(
@@ -47,7 +57,7 @@ export const Pre = forwardRef<HTMLPreElement, HTMLAttributes<HTMLPreElement>>(
return (
<pre
ref={ref}
className={cn('text-[13px] p-4 *:block *:w-full', className)}
className={cn('size-full *:block *:w-full', className)}
{...props}
>
{props.children}
@@ -66,6 +76,7 @@ export const CodeBlock = forwardRef<HTMLElement, CodeBlockProps>(
keepBackground = false,
icon,
viewportProps,
children,
...props
},
ref,
@@ -127,9 +138,19 @@ export const CodeBlock = forwardRef<HTMLElement, CodeBlockProps>(
<ScrollArea ref={areaRef} dir="ltr">
<ScrollViewport
{...viewportProps}
className={cn('max-h-[600px]', viewportProps?.className)}
className={cn(
'text-[13px] py-3.5 [&_.line]:px-4 max-h-[600px]',
props['data-line-numbers'] && '[&_.line]:pl-3',
viewportProps?.className,
)}
style={{
counterSet: props['data-line-numbers']
? `line ${Number(props['data-line-numbers-start'] ?? 1) - 1}`
: undefined,
...viewportProps?.style,
}}
>
{props.children}
{children}
</ScrollViewport>
<ScrollBar orientation="horizontal" />
</ScrollArea>