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
7 changes: 6 additions & 1 deletion packages/x/components/code-highlighter/CodeHighlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ const CodeHighlighter = React.forwardRef<HTMLDivElement, CodeHighlighterProps>((
style = {},
highlightProps,
prismLightMode = true,
showLineNumber,
wrapLongLines,
showCopyButton = true,
...restProps
} = props;

Expand Down Expand Up @@ -141,7 +144,7 @@ const CodeHighlighter = React.forwardRef<HTMLDivElement, CodeHighlighterProps>((
>
{lang}
</span>
<Actions.Copy text={children} />
{showCopyButton ? <Actions.Copy text={children} /> : null}
</div>
);
}
Expand All @@ -160,6 +163,8 @@ const CodeHighlighter = React.forwardRef<HTMLDivElement, CodeHighlighterProps>((
<Highlighter
language={lang}
wrapLines={true}
showLineNumbers={showLineNumber}
wrapLongLines={wrapLongLines}
Comment on lines +166 to +167

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C 5 'fallback|wrapLongLines|whiteSpace' \
  packages/x/components/code-highlighter/CodeHighlighter.tsx

Repository: ant-design/x

Length of output: 1051


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== locate package files =="
fd -a 'package.json|CodeHighlighter.tsx' . | sed 's#^\./##' | rg 'code-highlighter|package.json' | head -80

echo
echo "== package versions =="
python3 - <<'PY'
import json, os
for root, dirs, files in os.walk('packages'):
    for f in files:
        if f == 'package.json':
            p=os.path.join(root,f)
            try:
                data=json.load(open(p,encoding='utf-8'))
            except Exception as e:
                continue
            name=data.get('name','')
            deps={}
            for k in ('dependencies','devDependencies','peerDependencies'):
                deps.update(data.get(k,{}))
            if any('react-syntax' in v or name.endswith('code-highlighter') or 'code-highlighter' in name for v in deps.values()) or 'code-highlighter' in name:
                print(p)
                for k in deps:
                    for n,v in deps.get(k,{}).items():
                        if 'react-syntax' in n:
                            print(f"  {k}: {n}={v}")
PY

echo
echo "== code highlighter file size/context =="
wc -l packages/x/components/code-highlighter/CodeHighlighter.tsx
sed -n '1,220p' packages/x/components/code-highlighter/CodeHighlighter.tsx

echo
echo "== react-syntax-highlighter usage/imports =="
rg -n "react-syntax-highlighter|Highlighter" packages/x/components/code-highlighter packages -g '*.ts' -g '*.tsx' | head -120

Repository: ant-design/x

Length of output: 20968


🌐 Web query:

react-syntax-highlighter v16.1.0 wrapLongLines codeTagProps white-space pre-wrap pre

💡 Result:

In react-syntax-highlighter, the wrapLongLines prop is the intended mechanism for controlling line wrapping, as it explicitly toggles the white-space CSS property on the code tag [1][2][3]. When wrapLongLines is set to true, the library automatically applies white-space: pre-wrap to the code element [1][3]. Conversely, when it is false (the default), it applies white-space: pre [3]. It is recommended to use the wrapLongLines prop instead of manually overriding white-space via codeTagProps [3]. Manually setting white-space: pre-wrap via codeTagProps may be overridden by the library's internal styling, which explicitly sets white-space: pre by default [3]. If you encounter issues where lines are not wrapping despite using wrapLongLines (especially when combined with showLineNumbers), it is often due to the internal structure of the generated code block, which may require additional CSS adjustments such as adding flex-wrap: wrap to the line-level elements [4].

Citations:


Suspense fallback 遵守 wrapLongLines

当前代码在 wrapLongLines={false} 时仍使用 whiteSpace: 'pre-wrap' 作为 fallback;异步语言模块加载期间,fallback 会渲染换行,随后再切换为 react-syntax-highlighter 的 pre 样式。fallback 应与最终高亮使用相同的换行配置。

修正建议
-      fallback={<code style={{ whiteSpace: 'pre-wrap' }}>{children.replace(/\n$/, '')}</code>}
+      fallback={
+        <code style={{ whiteSpace: wrapLongLines ? 'pre-wrap' : 'pre' }}>
+          {children.replace(/\n$/, '')}
+        </code>
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/code-highlighter/CodeHighlighter.tsx` around lines 166
- 167, Update the Suspense fallback styling in CodeHighlighter so its whiteSpace
behavior is derived from wrapLongLines: use wrapping when enabled and preserve
pre-style whitespace when disabled, matching the final react-syntax-highlighter
rendering.

Source: MCP tools

style={customOneLight}
codeTagProps={{ style: { background: 'transparent' } }}
{...highlightProps}
Expand Down
78 changes: 78 additions & 0 deletions packages/x/components/code-highlighter/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -522,4 +522,82 @@ describe('CodeHighlighter', () => {
process.env.NODE_ENV = originalEnv;
});
});

describe('flexible config', () => {
it('should show line numbers when showLineNumber is true', async () => {
const { container } = render(
<CodeHighlighter lang="javascript" showLineNumber>
{`console.log("test");`}
</CodeHighlighter>,
);
await waitFor(() => {
expect(container.querySelector('pre')).toBeInTheDocument();
});
expect(container.querySelector('.linenumber')).toBeInTheDocument();
});

it('should not show line numbers by default', async () => {
const { container } = render(
<CodeHighlighter lang="javascript">{`console.log("test");`}</CodeHighlighter>,
);
await waitFor(() => {
expect(container.querySelector('pre')).toBeInTheDocument();
});
expect(container.querySelector('.linenumber')).not.toBeInTheDocument();
});

it('should pass wrapLongLines to SyntaxHighlighter', async () => {
const { container } = render(
<CodeHighlighter lang="javascript" wrapLongLines>
{`console.log("test");`}
</CodeHighlighter>,
);
await waitFor(() => {
expect(container.querySelector('code')).toBeInTheDocument();
});
// wrapLongLines sets whiteSpace: pre-wrap on the rendered code element
expect(container.querySelector('code')?.style.whiteSpace).toBe('pre-wrap');
});
Comment on lines +549 to +560

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -A14 -B4 'should pass wrapLongLines' \
  packages/x/components/code-highlighter/__tests__/index.test.tsx

Repository: ant-design/x

Length of output: 931


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== files =="
git ls-files packages/x/components/code-highlight | head -50

echo "== component outline =="
ast-grep outline packages/x/components/code-highlight -view expanded || true

echo "== relevant source snippets =="
for f in $(git ls-files packages/x/components/code-highlight | grep -E '\.(tsx|ts|jsx|js)$' | head -20); do
  if rg -n "function CodeHighlighter|const CodeHighlighter|SyntaxHighlighter|wrapLongLines|fallback|Suspense|react-syntax-highlighter" "$f" >/dev/null; then
    echo "--- $f ---"
    rg -n -A30 -B30 "function CodeHighlighter|const CodeHighlighter|SyntaxHighlighter|wrapLongLines|fallback|Suspense|react-syntax-highlighter" "$f" | sed -n '1,220p'
  fi
done

echo "== test imports and surrounding tests =="
sed -n '1,90p;510,580p' packages/x/components/code-highlight/__tests__/index.test.tsx

Repository: ant-design/x

Length of output: 529


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== all code-highlight paths =="
git ls-files | grep -E '(^|/)code[-_]highlight' || true

echo "== exact test file =="
git ls-files | grep 'CodeHighlighter.*test\.tsx$' || true
git ls-files | grep 'index\.test\.tsx$' | grep -i 'highlight' || true

echo "== files matching CodeHighlighter =="
git ls-files | xargs rg -l "CodeHighlighter" || true

echo "== package directory snapshot =="
for d in $(git ls-files | sed -n 's#^packages/[^/]*$#&`#p`' | sort | head -50); do
  true
done
git ls-files packages | grep code-highlight || true

Repository: ant-design/x

Length of output: 3521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CodeHighlighter.tsx size =="
wc -l packages/x/components/code-highlighter/CodeHighlighter.tsx
echo

echo "== CodeHighlighter.tsx outline =="
ast-grep outline packages/x/components/code-highlighter/CodeHighlighter.tsx --view expanded || true
echo

echo "== CodeHighlighter.tsx relevant lines =="
sed -n '1,260p' packages/x/components/code-highlighter/CodeHighlighter.tsx | cat -n
echo

echo "== Tests relevant section =="
sed -n '530,565p' packages/x/components/code-highlighter/__tests__/index.test.tsx | cat -n

Repository: ant-design/x

Length of output: 9625


🌐 Web query:

react-syntax-highlighter wrapLongLines whiteSpace code element pre code

💡 Result:

In react-syntax-highlighter, the wrapLongLines prop is a boolean used to control how code blocks handle long lines [1][2]. When set to true, it applies white-space: pre-wrap to the code element, allowing text to wrap within the container instead of overflowing [1][3]. When set to false (the default), it uses white-space: pre [1][2]. Key technical details and behaviors include: 1. Automatic Line Wrapping: Enabling wrapLongLines effectively overrides the need to manually apply white-space styling via CSS or codeTagProps [3]. It also forces each line of code to be wrapped in its own span element, similar to how wrapLines={true} behaves [3]. 2. Interaction with showLineNumbers: When both wrapLongLines and showLineNumbers are enabled, the library applies display: flex to the line elements to ensure line numbers remain correctly aligned with their corresponding code [3]. 3. Known Issues and Workarounds: - Flexbox Conflicts: Users have reported that the library's internal use of display: flex can sometimes interfere with custom styling provided via lineProps [4]. If you need to override these styles, it is often necessary to use more specific CSS or target the wrapper elements directly [4][5]. - Prism.js Inconsistencies: Some users have noted that wrapLongLines may not behave as expected with Prism.js in certain versions [6]. A common workaround for persistent wrapping issues is to force the style directly using the codeTagProps prop: codeTagProps={{ style: { whiteSpace: 'pre-wrap' } }} [6]. - Layout Alignment: When using wrapLongLines and showLineNumbers together, long lines may occasionally cause layout misalignment. Recent updates have introduced word-break: break-word and nested structures to help mitigate these visual issues [7].

Citations:


等待最终高亮 DOM 再检查 wrapLongLines 效果。

fallback 直接渲染 <code style={{ whiteSpace: 'pre-wrap' }}>,所以当前 waitFor 可能在 SyntaxHighlighter 加载前通过,断言值也不能直接反映 props 生效情况。建议先等待 pre,再断言 pre codewhiteSpace;如需更明确,可使用更长或带换行的代码内容。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/code-highlighter/__tests__/index.test.tsx` around lines
549 - 560, Update the wrapLongLines test around CodeHighlighter so it waits for
the final SyntaxHighlighter DOM by targeting the rendered pre element, then
assert whiteSpace on the nested pre code element. Use content with a line break
or sufficient length if needed, ensuring the assertion verifies the prop
behavior rather than the fallback code path.

Source: MCP tools


it('should show copy button by default', async () => {
const { container } = render(
<CodeHighlighter lang="javascript">{`console.log("test");`}</CodeHighlighter>,
);
await waitFor(() => {
expect(container.querySelector('.ant-codeHighlighter-header')).toBeInTheDocument();
});
expect(
container.querySelector('.ant-codeHighlighter-header .ant-actions-copy'),
).toBeInTheDocument();
});

it('should hide copy button when showCopyButton is false', async () => {
const { container } = render(
<CodeHighlighter lang="javascript" showCopyButton={false}>
{`console.log("test");`}
</CodeHighlighter>,
);
await waitFor(() => {
expect(container.querySelector('.ant-codeHighlighter-header')).toBeInTheDocument();
});
expect(
container.querySelector('.ant-codeHighlighter-header .ant-actions-copy'),
).not.toBeInTheDocument();
});

it('should not affect custom header when showCopyButton is false', async () => {
const { container } = render(
<CodeHighlighter
lang="javascript"
showCopyButton={false}
header={<div className="myCustomHeader">custom</div>}
>
{`console.log("test");`}
</CodeHighlighter>,
);
await waitFor(() => {
expect(container.querySelector('.myCustomHeader')).toBeInTheDocument();
});
});
});
});
53 changes: 53 additions & 0 deletions packages/x/components/code-highlighter/demo/flexible-config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { CodeHighlighter } from '@ant-design/x';
import React from 'react';

const App: React.FC = () => {
const code = `import React from 'react';
import { Button } from 'antd';

const App = () => (
<div>
<Button type="primary">Primary Button</Button>
</div>
);

export default App;`;

const longLineCode = `const aVeryLongVariableName = someFunction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
console.log(aVeryLongVariableName);`;

return (
<div>
<h3 style={{ marginBottom: 8 }}>显示行号</h3>
<p style={{ marginBottom: 8, color: '#666' }}>
通过 <code>showLineNumber</code> 显示代码行号。
</p>
<CodeHighlighter lang="javascript" showLineNumber>
{code}
</CodeHighlighter>

<h3 style={{ margin: '8px 0' }}>自动换行</h3>
<p style={{ marginBottom: 8, color: '#666' }}>
通过 <code>wrapLongLines</code> 让超长行自动换行,无需横向滚动。
</p>
<CodeHighlighter lang="javascript" wrapLongLines>
{longLineCode}
</CodeHighlighter>

<h3 style={{ margin: '8px 0' }}>隐藏复制按钮</h3>
<p style={{ marginBottom: 8, color: '#666' }}>
通过 <code>showCopyButton={'{false}'}</code> 隐藏默认 Header 中的复制按钮。
</p>
<CodeHighlighter lang="javascript" showCopyButton={false}>
{code}
</CodeHighlighter>

<h3 style={{ margin: '8px 0' }}>组合使用</h3>
<CodeHighlighter lang="javascript" showLineNumber wrapLongLines showCopyButton={false}>
{longLineCode}
</CodeHighlighter>
</div>
);
};

export default App;
4 changes: 4 additions & 0 deletions packages/x/components/code-highlighter/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The CodeHighlighter component is used in scenarios where you need to display cod
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">Basic</code>
<code src="./demo/custom-header.tsx">Custom Header</code>
<code src="./demo/flexible-config.tsx">Flexible Config</code>
<code src="./demo/with-xmarkdown.tsx">With XMarkdown</code>

## API
Expand All @@ -38,6 +39,9 @@ For common properties, refer to: [Common Properties](/docs/react/common-props).
| className | Style class name | `string` | |
| classNames | Style class names | `string` | - |
| highlightProps | Code highlighting configuration | [`highlightProps`](https://github.com/react-syntax-highlighter/react-syntax-highlighter?tab=readme-ov-file#props) | - |
| showLineNumber | Whether to show line numbers | `boolean` | `false` |
| wrapLongLines | Whether to wrap long lines | `boolean` | `false` |
| showCopyButton | Whether to show the copy button, only works with the default header | `boolean` | `true` |
| prismLightMode | Whether to use Prism light mode to automatically load language support based on lang prop for smaller bundle size | `boolean` | `true` |

### CodeHighlighterRef
Expand Down
4 changes: 4 additions & 0 deletions packages/x/components/code-highlighter/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ CodeHighlighter 组件用于需要展示带有语法高亮的代码片段的场
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">基本</code>
<code src="./demo/custom-header.tsx">自定义 Header</code>
<code src="./demo/flexible-config.tsx">灵活配置</code>
<code src="./demo/with-xmarkdown.tsx">配合 XMarkdown</code>

## API
Expand All @@ -37,6 +38,9 @@ CodeHighlighter 组件用于需要展示带有语法高亮的代码片段的场
| children | 代码内容 | `string` | - |
| header | 头部内容,为 `false` 时不显示头部 | `React.ReactNode \| (() => React.ReactNode \| false) \| false` | - |
| highlightProps | 代码高亮配置,透传给 react-syntax-highlighter | [`SyntaxHighlighterProps`](https://github.com/react-syntax-highlighter/react-syntax-highlighter?tab=readme-ov-file#props) | - |
| showLineNumber | 是否显示行号 | `boolean` | `false` |
| wrapLongLines | 是否自动换行 | `boolean` | `false` |
| showCopyButton | 是否显示复制按钮,仅在默认 header 下生效 | `boolean` | `true` |
| prismLightMode | 是否使用 Prism 轻量模式,根据 `lang` 自动按需加载语言支持以减少打包体积 | `boolean` | `true` |

### CodeHighlighterRef
Expand Down
18 changes: 18 additions & 0 deletions packages/x/components/code-highlighter/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ export interface CodeHighlighterProps
* @descEN Additional props for syntax highlighter
*/
highlightProps?: Partial<SyntaxHighlighterProps>;
/**
* @desc 是否显示行号
* @descEN Whether to show line numbers
* @default false
*/
showLineNumber?: boolean;
/**
* @desc 是否自动换行
* @descEN Whether to wrap long lines
* @default false
*/
wrapLongLines?: boolean;
/**
* @desc 是否显示复制按钮,仅在默认 header 下生效
* @descEN Whether to show the copy button, only works with the default header
* @default true
*/
showCopyButton?: boolean;
/**
* @desc 语义化结构 className
* @descEN Semantic structure class names
Expand Down