mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
🐛 Fix: Resolve Markdown list display issues on Windows (#693)
This commit is contained in:
@@ -9,7 +9,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|||||||
import { MarkdownDisplay } from './MarkdownDisplay.js';
|
import { MarkdownDisplay } from './MarkdownDisplay.js';
|
||||||
import { LoadedSettings } from '../../config/settings.js';
|
import { LoadedSettings } from '../../config/settings.js';
|
||||||
import { SettingsContext } from '../contexts/SettingsContext.js';
|
import { SettingsContext } from '../contexts/SettingsContext.js';
|
||||||
import { EOL } from 'node:os';
|
|
||||||
|
|
||||||
describe('<MarkdownDisplay />', () => {
|
describe('<MarkdownDisplay />', () => {
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
@@ -57,7 +56,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
## Header 2
|
## Header 2
|
||||||
### Header 3
|
### Header 3
|
||||||
#### Header 4
|
#### Header 4
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -67,10 +66,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders a fenced code block with a language', () => {
|
it('renders a fenced code block with a language', () => {
|
||||||
const text = '```javascript\nconst x = 1;\nconsole.log(x);\n```'.replace(
|
const text = '```javascript\nconst x = 1;\nconsole.log(x);\n```';
|
||||||
/\n/g,
|
|
||||||
EOL,
|
|
||||||
);
|
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -80,7 +76,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders a fenced code block without a language', () => {
|
it('renders a fenced code block without a language', () => {
|
||||||
const text = '```\nplain text\n```'.replace(/\n/g, EOL);
|
const text = '```\nplain text\n```';
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -90,7 +86,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles unclosed (pending) code blocks', () => {
|
it('handles unclosed (pending) code blocks', () => {
|
||||||
const text = '```typescript\nlet y = 2;'.replace(/\n/g, EOL);
|
const text = '```typescript\nlet y = 2;';
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} isPending={true} />
|
<MarkdownDisplay {...baseProps} text={text} isPending={true} />
|
||||||
@@ -104,7 +100,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
- item A
|
- item A
|
||||||
* item B
|
* item B
|
||||||
+ item C
|
+ item C
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -118,7 +114,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
* Level 1
|
* Level 1
|
||||||
* Level 2
|
* Level 2
|
||||||
* Level 3
|
* Level 3
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -131,7 +127,7 @@ describe('<MarkdownDisplay />', () => {
|
|||||||
const text = `
|
const text = `
|
||||||
1. First item
|
1. First item
|
||||||
2. Second item
|
2. Second item
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -147,7 +143,7 @@ Hello
|
|||||||
World
|
World
|
||||||
***
|
***
|
||||||
Test
|
Test
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -162,7 +158,7 @@ Test
|
|||||||
|----------|:--------:|
|
|----------|:--------:|
|
||||||
| Cell 1 | Cell 2 |
|
| Cell 1 | Cell 2 |
|
||||||
| Cell 3 | Cell 4 |
|
| Cell 3 | Cell 4 |
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -176,7 +172,7 @@ Test
|
|||||||
Some text before.
|
Some text before.
|
||||||
| A | B |
|
| A | B |
|
||||||
|---|
|
|---|
|
||||||
| 1 | 2 |`.replace(/\n/g, EOL);
|
| 1 | 2 |`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -188,7 +184,7 @@ Some text before.
|
|||||||
it('inserts a single space between paragraphs', () => {
|
it('inserts a single space between paragraphs', () => {
|
||||||
const text = `Paragraph 1.
|
const text = `Paragraph 1.
|
||||||
|
|
||||||
Paragraph 2.`.replace(/\n/g, EOL);
|
Paragraph 2.`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -211,7 +207,7 @@ some code
|
|||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Another paragraph.
|
Another paragraph.
|
||||||
`.replace(/\n/g, EOL);
|
`;
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -221,7 +217,7 @@ Another paragraph.
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('hides line numbers in code blocks when showLineNumbers is false', () => {
|
it('hides line numbers in code blocks when showLineNumbers is false', () => {
|
||||||
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
|
const text = '```javascript\nconst x = 1;\n```';
|
||||||
const settings = new LoadedSettings(
|
const settings = new LoadedSettings(
|
||||||
{ path: '', settings: {} },
|
{ path: '', settings: {} },
|
||||||
{ path: '', settings: {} },
|
{ path: '', settings: {} },
|
||||||
@@ -242,7 +238,7 @@ Another paragraph.
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows line numbers in code blocks by default', () => {
|
it('shows line numbers in code blocks by default', () => {
|
||||||
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
|
const text = '```javascript\nconst x = 1;\n```';
|
||||||
const { lastFrame } = render(
|
const { lastFrame } = render(
|
||||||
<SettingsContext.Provider value={mockSettings}>
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
<MarkdownDisplay {...baseProps} text={text} />
|
<MarkdownDisplay {...baseProps} text={text} />
|
||||||
@@ -251,4 +247,21 @@ Another paragraph.
|
|||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
expect(lastFrame()).toContain(' 1 ');
|
expect(lastFrame()).toContain(' 1 ');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('correctly splits lines using \\n regardless of platform EOL', () => {
|
||||||
|
// Test that the component uses \n for splitting, not EOL
|
||||||
|
const textWithUnixLineEndings = 'Line 1\nLine 2\nLine 3';
|
||||||
|
|
||||||
|
const { lastFrame } = render(
|
||||||
|
<SettingsContext.Provider value={mockSettings}>
|
||||||
|
<MarkdownDisplay {...baseProps} text={textWithUnixLineEndings} />
|
||||||
|
</SettingsContext.Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = lastFrame();
|
||||||
|
expect(output).toContain('Line 1');
|
||||||
|
expect(output).toContain('Line 2');
|
||||||
|
expect(output).toContain('Line 3');
|
||||||
|
expect(output).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Box } from 'ink';
|
import { Text, Box } from 'ink';
|
||||||
import { EOL } from 'node:os';
|
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import { colorizeCode } from './CodeColorizer.js';
|
import { colorizeCode } from './CodeColorizer.js';
|
||||||
import { TableRenderer } from './TableRenderer.js';
|
import { TableRenderer } from './TableRenderer.js';
|
||||||
@@ -35,7 +34,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!text) return <></>;
|
if (!text) return <></>;
|
||||||
|
|
||||||
const lines = text.split(EOL);
|
const lines = text.split(`\n`);
|
||||||
const headerRegex = /^ *(#{1,4}) +(.*)/;
|
const headerRegex = /^ *(#{1,4}) +(.*)/;
|
||||||
const codeFenceRegex = /^ *(`{3,}|~{3,}) *(\w*?) *$/;
|
const codeFenceRegex = /^ *(`{3,}|~{3,}) *(\w*?) *$/;
|
||||||
const ulItemRegex = /^([ \t]*)([-*+]) +(.*)/;
|
const ulItemRegex = /^([ \t]*)([-*+]) +(.*)/;
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ Another paragraph.
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`<MarkdownDisplay /> > correctly splits lines using \\n regardless of platform EOL 1`] = `
|
||||||
|
"Line 1
|
||||||
|
Line 2
|
||||||
|
Line 3"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`<MarkdownDisplay /> > handles a table at the end of the input 1`] = `
|
exports[`<MarkdownDisplay /> > handles a table at the end of the input 1`] = `
|
||||||
"Some text before.
|
"Some text before.
|
||||||
| A | B |
|
| A | B |
|
||||||
|
|||||||
Reference in New Issue
Block a user