Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

import React, {type ComponentProps, type ReactNode} from 'react';
import clsx from 'clsx';
import {useCodeBlockContext} from '@docusaurus/theme-common/internal';
import {usePrismTheme} from '@docusaurus/theme-common';
import {
parseCodeLinesFromTokens,
useCodeBlockContext,
} from '@docusaurus/theme-common/internal';
import {usePrismTheme, useThemeConfig} from '@docusaurus/theme-common';
import {Highlight} from 'prism-react-renderer';
import type {Props} from '@theme/CodeBlock/Content';
import Line from '@theme/CodeBlock/Line';
Expand Down Expand Up @@ -57,28 +60,39 @@ export default function CodeBlockContent({
}: Props): ReactNode {
const {metadata, wordWrap} = useCodeBlockContext();
const prismTheme = usePrismTheme();
const {code, language, lineNumbersStart, lineClassNames} = metadata;
const {prism} = useThemeConfig();
const {codeInput, language, lineNumbersStart, metastring} = metadata;
return (
<Highlight theme={prismTheme} code={code} language={language}>
{({className, style, tokens: lines, getLineProps, getTokenProps}) => (
<Pre
ref={wordWrap.codeBlockRef}
className={clsx(classNameProp, className)}
style={style}>
<Code>
{lines.map((line, i) => (
<Line
key={i}
line={line}
getLineProps={getLineProps}
getTokenProps={getTokenProps}
classNames={lineClassNames[i]}
showLineNumbers={lineNumbersStart !== undefined}
/>
))}
</Code>
</Pre>
)}
<Highlight theme={prismTheme} code={codeInput} language={language}>
{({className, style, tokens: lines, getLineProps, getTokenProps}) => {
const {lineClassNames, lineIndexes} = parseCodeLinesFromTokens({
codeInput,
tokens: lines,
metastring,
magicComments: prism.magicComments,
language,
});
const visibleLines = lineIndexes.map((index) => lines[index]!);
return (
<Pre
ref={wordWrap.codeBlockRef}
className={clsx(classNameProp, className)}
style={style}>
<Code>
{visibleLines.map((line, i) => (
<Line
key={i}
line={line}
getLineProps={getLineProps}
getTokenProps={getTokenProps}
classNames={lineClassNames[i]}
showLineNumbers={lineNumbersStart !== undefined}
/>
))}
</Code>
</Pre>
);
}}
</Highlight>
);
}
1 change: 1 addition & 0 deletions packages/docusaurus-theme-common/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export {
parseCodeBlockTitle,
parseClassNameLanguage as parseLanguage,
parseLines,
parseCodeLinesFromTokens,
getLineNumbersStart,
containsLineNumbers,
} from './utils/codeBlockUtils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import {
getLineNumbersStart,
type MagicCommentConfig,
parseCodeLinesFromTokens,
parseCodeBlockTitle,
parseClassNameLanguage,
parseLines,
Expand Down Expand Up @@ -812,6 +813,65 @@ describe('getLineNumbersStart', () => {
});
});

describe('parseCodeLinesFromTokens', () => {
function createTokens(code: string) {
return code.split(/\r?\n/).map((line) => [{content: line}]);
}

it('removes magic comment lines and returns line indexes', () => {
const codeInput = `// highlight-next-line\nconst x = 42;`;
const result = parseCodeLinesFromTokens({
codeInput,
tokens: createTokens(codeInput),
metastring: '',
language: 'js',
magicComments: defaultMagicComments,
});

expect(result.lineClassNames).toMatchInlineSnapshot(`
{
"0": [
"theme-code-block-highlighted-line",
],
}
`);
expect(result.lineIndexes).toEqual([1]);
});

it('respects metastring ranges', () => {
const codeInput = `const x = 42;`;
const result = parseCodeLinesFromTokens({
codeInput,
tokens: createTokens(codeInput),
metastring: '{1}',
language: 'js',
magicComments: defaultMagicComments,
});

expect(result.lineClassNames).toMatchInlineSnapshot(`
{
"0": [
"theme-code-block-highlighted-line",
],
}
`);
expect(result.lineIndexes).toEqual([0]);
});

it('drops trailing empty line when input ends with newline', () => {
const codeInput = `const x = 42;\n`;
const result = parseCodeLinesFromTokens({
codeInput,
tokens: createTokens(codeInput),
metastring: '',
language: 'js',
magicComments: defaultMagicComments,
});

expect(result.lineIndexes).toEqual([0]);
});
});

describe('createCodeBlockMetadata', () => {
type Params = Parameters<typeof createCodeBlockMetadata>[0];

Expand Down Expand Up @@ -840,6 +900,7 @@ describe('createCodeBlockMetadata', () => {
"language": "text",
"lineClassNames": {},
"lineNumbersStart": undefined,
"metastring": "",
"title": undefined,
}
`);
Expand Down
64 changes: 58 additions & 6 deletions packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,20 +248,20 @@ function parseCodeLinesFromMetastring(
return null;
}

function parseCodeLinesFromContent(
code: string,
function parseCodeLinesFromLineArray(
lines: string[],
params: ParseCodeLinesParam,
): ParsedCodeLines {
): {lineClassNames: CodeLineClassNames; lineIndexes: number[]} {
const {language, magicComments} = params;
const lineIndexes = lines.map((_, index) => index);
if (language === undefined) {
return {lineClassNames: {}, code};
return {lineClassNames: {}, lineIndexes};
}
const directiveRegex = getAllMagicCommentDirectiveStyles(
language,
magicComments,
);
// Go through line by line
const lines = code.split(/\r?\n/);
const blocks = Object.fromEntries(
magicComments.map((d) => [d.className, {start: 0, range: ''}]),
);
Expand Down Expand Up @@ -301,6 +301,7 @@ function parseCodeLinesFromContent(
}-${lineNumber - 1},`;
}
lines.splice(lineNumber, 1);
lineIndexes.splice(lineNumber, 1);
}

const lineClassNames: {[lineIndex: number]: string[]} = {};
Expand All @@ -311,7 +312,16 @@ function parseCodeLinesFromContent(
});
});

return {code: lines.join('\n'), lineClassNames};
return {lineClassNames, lineIndexes};
}

function parseCodeLinesFromContent(
code: string,
params: ParseCodeLinesParam,
): ParsedCodeLines {
const lines = code.split(/\r?\n/);
const {lineClassNames} = parseCodeLinesFromLineArray(lines, params);
return {lineClassNames, code: lines.join('\n')};
}

/**
Expand All @@ -337,6 +347,46 @@ export function parseLines(
);
}

type TokenLine<T extends {content: string}> = T[];

export function parseCodeLinesFromTokens<T extends {content: string}>(
params: {
codeInput: string;
tokens: TokenLine<T>[];
} & ParseCodeLinesParam,
): {
lineClassNames: CodeLineClassNames;
lineIndexes: number[];
} {
const {codeInput, tokens, metastring, magicComments, language} = params;
const lines = tokens.map((line) =>
line.map((token) => token.content).join(''),
);

if (codeInput.match(/\r?\n$/) && lines.at(-1) === '') {
lines.pop();
}

const metastringResult = parseCodeLinesFromMetastring(lines.join('\n'), {
metastring,
magicComments,
language,
});

if (metastringResult) {
return {
lineClassNames: metastringResult.lineClassNames,
lineIndexes: lines.map((_, index) => index),
};
}

return parseCodeLinesFromLineArray(lines, {
metastring,
magicComments,
language,
});
}

/**
* Gets the language name from the class name (set by MDX).
* e.g. `"language-javascript"` => `"javascript"`.
Expand Down Expand Up @@ -402,6 +452,7 @@ export interface CodeBlockMetadata {
className: string; // There's always a "language-<lang>" className
language: string;
title: ReactNode;
metastring: string | undefined;
lineNumbersStart: number | undefined;
lineClassNames: CodeLineClassNames;
}
Expand Down Expand Up @@ -446,6 +497,7 @@ export function createCodeBlockMetadata(params: {
className,
language,
title,
metastring: params.metastring,
lineNumbersStart,
lineClassNames,
};
Expand Down
3 changes: 3 additions & 0 deletions website/netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

[context.deploy-preview]
command = "(echo 'Build packages start' && yarn --cwd .. build:packages && echo 'Build packages end') & (echo 'Git backfill start' && git backfill && echo 'Git backfill end' ) & wait && yarn netlify:build:deployPreview"
[context.deploy-preview.environment]
DISABLE_RSPACK_INCREMENTAL = "true"
DOCUSAURUS_NO_PERSISTENT_CACHE = "true"

[[plugins]]
package = "netlify-plugin-cache"
Expand Down