diff --git a/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx b/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx
index 634888b07c..927a69e154 100644
--- a/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx
+++ b/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx
@@ -7,7 +7,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MarkdownDisplay } from './MarkdownDisplay.js';
import { LoadedSettings } from '../../config/settings.js';
-import { EOL } from 'node:os';
import { renderWithProviders } from '../../test-utils/render.js';
describe('', () => {
@@ -36,132 +35,138 @@ describe('', () => {
expect(lastFrame()).toMatchSnapshot();
});
- it('renders headers with correct levels', () => {
- const text = `
+ const lineEndings = [
+ { name: 'Windows', eol: '\r\n' },
+ { name: 'Unix', eol: '\n' },
+ ];
+
+ describe.each(lineEndings)('with $name line endings', ({ eol }) => {
+ it('renders headers with correct levels', () => {
+ const text = `
# Header 1
## Header 2
### Header 3
#### Header 4
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders a fenced code block with a language', () => {
- const text = '```javascript\nconst x = 1;\nconsole.log(x);\n```'.replace(
- /\n/g,
- EOL,
- );
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+ it('renders a fenced code block with a language', () => {
+ const text = '```javascript\nconst x = 1;\nconsole.log(x);\n```'.replace(
+ /\n/g,
+ eol,
+ );
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders a fenced code block without a language', () => {
- const text = '```\nplain text\n```'.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+ it('renders a fenced code block without a language', () => {
+ const text = '```\nplain text\n```'.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('handles unclosed (pending) code blocks', () => {
- const text = '```typescript\nlet y = 2;'.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+ it('handles unclosed (pending) code blocks', () => {
+ const text = '```typescript\nlet y = 2;'.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders unordered lists with different markers', () => {
- const text = `
+ it('renders unordered lists with different markers', () => {
+ const text = `
- item A
* item B
+ item C
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders nested unordered lists', () => {
- const text = `
+ it('renders nested unordered lists', () => {
+ const text = `
* Level 1
* Level 2
* Level 3
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders ordered lists', () => {
- const text = `
+ it('renders ordered lists', () => {
+ const text = `
1. First item
2. Second item
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders horizontal rules', () => {
- const text = `
+ it('renders horizontal rules', () => {
+ const text = `
Hello
---
World
***
Test
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('renders tables correctly', () => {
- const text = `
+ it('renders tables correctly', () => {
+ const text = `
| Header 1 | Header 2 |
|----------|:--------:|
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('handles a table at the end of the input', () => {
- const text = `
+ it('handles a table at the end of the input', () => {
+ const text = `
Some text before.
| A | B |
|---|
-| 1 | 2 |`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+| 1 | 2 |`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('inserts a single space between paragraphs', () => {
- const text = `Paragraph 1.
+ it('inserts a single space between paragraphs', () => {
+ const text = `Paragraph 1.
-Paragraph 2.`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+Paragraph 2.`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('correctly parses a mix of markdown elements', () => {
- const text = `
+ it('correctly parses a mix of markdown elements', () => {
+ const text = `
# Main Title
Here is a paragraph.
@@ -174,42 +179,43 @@ some code
\`\`\`
Another paragraph.
-`.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- });
+`.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ });
- it('hides line numbers in code blocks when showLineNumbers is false', () => {
- const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
- const settings = new LoadedSettings(
- { path: '', settings: {}, originalSettings: {} },
- { path: '', settings: {}, originalSettings: {} },
- {
- path: '',
- settings: { ui: { showLineNumbers: false } },
- originalSettings: { ui: { showLineNumbers: false } },
- },
- { path: '', settings: {}, originalSettings: {} },
- true,
- new Set(),
- );
+ it('hides line numbers in code blocks when showLineNumbers is false', () => {
+ const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, eol);
+ const settings = new LoadedSettings(
+ { path: '', settings: {}, originalSettings: {} },
+ { path: '', settings: {}, originalSettings: {} },
+ {
+ path: '',
+ settings: { ui: { showLineNumbers: false } },
+ originalSettings: { ui: { showLineNumbers: false } },
+ },
+ { path: '', settings: {}, originalSettings: {} },
+ true,
+ new Set(),
+ );
- const { lastFrame } = renderWithProviders(
- ,
- { settings },
- );
- expect(lastFrame()).toMatchSnapshot();
- expect(lastFrame()).not.toContain(' 1 ');
- });
+ const { lastFrame } = renderWithProviders(
+ ,
+ { settings },
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ expect(lastFrame()).not.toContain(' 1 ');
+ });
- it('shows line numbers in code blocks by default', () => {
- const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
- const { lastFrame } = renderWithProviders(
- ,
- );
- expect(lastFrame()).toMatchSnapshot();
- expect(lastFrame()).toContain(' 1 ');
+ it('shows line numbers in code blocks by default', () => {
+ const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, eol);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ expect(lastFrame()).toContain(' 1 ');
+ });
});
});
diff --git a/packages/cli/src/ui/utils/MarkdownDisplay.tsx b/packages/cli/src/ui/utils/MarkdownDisplay.tsx
index 2baea99884..da6bf21aaf 100644
--- a/packages/cli/src/ui/utils/MarkdownDisplay.tsx
+++ b/packages/cli/src/ui/utils/MarkdownDisplay.tsx
@@ -6,7 +6,6 @@
import React from 'react';
import { Text, Box } from 'ink';
-import { EOL } from 'node:os';
import { theme } from '../semantic-colors.js';
import { colorizeCode } from './CodeColorizer.js';
import { TableRenderer } from './TableRenderer.js';
@@ -35,7 +34,7 @@ const MarkdownDisplayInternal: React.FC = ({
}) => {
if (!text) return <>>;
- const lines = text.split(EOL);
+ const lines = text.split(/\r?\n/);
const headerRegex = /^ *(#{1,4}) +(.*)/;
const codeFenceRegex = /^ *(`{3,}|~{3,}) *(\w*?) *$/;
const ulItemRegex = /^([ \t]*)([-*+]) +(.*)/;
diff --git a/packages/cli/src/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap b/packages/cli/src/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap
index 223c293b19..c340dc8f61 100644
--- a/packages/cli/src/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap
+++ b/packages/cli/src/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap
@@ -1,6 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[` > correctly parses a mix of markdown elements 1`] = `
+exports[` > renders a simple paragraph 1`] = `"Hello, world."`;
+
+exports[` > renders nothing for empty text 1`] = `""`;
+
+exports[` > with 'Unix' line endings > correctly parses a mix of markdown elements 1`] = `
"Main Title
Here is a paragraph.
@@ -14,33 +18,31 @@ Another paragraph.
"
`;
-exports[` > handles a table at the end of the input 1`] = `
+exports[` > with 'Unix' line endings > handles a table at the end of the input 1`] = `
"Some text before.
| A | B |
|---|
| 1 | 2 |"
`;
-exports[` > handles unclosed (pending) code blocks 1`] = `" 1 let y = 2;"`;
+exports[` > with 'Unix' line endings > handles unclosed (pending) code blocks 1`] = `" 1 let y = 2;"`;
-exports[` > hides line numbers in code blocks when showLineNumbers is false 1`] = `" const x = 1;"`;
+exports[` > with 'Unix' line endings > hides line numbers in code blocks when showLineNumbers is false 1`] = `" const x = 1;"`;
-exports[` > inserts a single space between paragraphs 1`] = `
+exports[` > with 'Unix' line endings > inserts a single space between paragraphs 1`] = `
"Paragraph 1.
Paragraph 2."
`;
-exports[` > renders a fenced code block with a language 1`] = `
+exports[` > with 'Unix' line endings > renders a fenced code block with a language 1`] = `
" 1 const x = 1;
2 console.log(x);"
`;
-exports[` > renders a fenced code block without a language 1`] = `" 1 plain text"`;
+exports[` > with 'Unix' line endings > renders a fenced code block without a language 1`] = `" 1 plain text"`;
-exports[` > renders a simple paragraph 1`] = `"Hello, world."`;
-
-exports[` > renders headers with correct levels 1`] = `
+exports[` > with 'Unix' line endings > renders headers with correct levels 1`] = `
"Header 1
Header 2
Header 3
@@ -48,7 +50,7 @@ Header 4
"
`;
-exports[` > renders horizontal rules 1`] = `
+exports[` > with 'Unix' line endings > renders horizontal rules 1`] = `
"Hello
---
World
@@ -57,22 +59,20 @@ Test
"
`;
-exports[` > renders nested unordered lists 1`] = `
+exports[` > with 'Unix' line endings > renders nested unordered lists 1`] = `
" * Level 1
* Level 2
* Level 3
"
`;
-exports[` > renders nothing for empty text 1`] = `""`;
-
-exports[` > renders ordered lists 1`] = `
+exports[` > with 'Unix' line endings > renders ordered lists 1`] = `
" 1. First item
2. Second item
"
`;
-exports[` > renders tables correctly 1`] = `
+exports[` > with 'Unix' line endings > renders tables correctly 1`] = `
"
┌──────────┬──────────┐
│ Header 1 │ Header 2 │
@@ -83,11 +83,99 @@ exports[` > renders tables correctly 1`] = `
"
`;
-exports[` > renders unordered lists with different markers 1`] = `
+exports[` > with 'Unix' line endings > renders unordered lists with different markers 1`] = `
" - item A
* item B
+ item C
"
`;
-exports[` > shows line numbers in code blocks by default 1`] = `" 1 const x = 1;"`;
+exports[` > with 'Unix' line endings > shows line numbers in code blocks by default 1`] = `" 1 const x = 1;"`;
+
+exports[` > with 'Windows' line endings > correctly parses a mix of markdown elements 1`] = `
+"Main Title
+
+Here is a paragraph.
+
+ - List item 1
+ - List item 2
+
+ 1 some code
+
+Another paragraph.
+"
+`;
+
+exports[` > with 'Windows' line endings > handles a table at the end of the input 1`] = `
+"Some text before.
+| A | B |
+|---|
+| 1 | 2 |"
+`;
+
+exports[` > with 'Windows' line endings > handles unclosed (pending) code blocks 1`] = `" 1 let y = 2;"`;
+
+exports[` > with 'Windows' line endings > hides line numbers in code blocks when showLineNumbers is false 1`] = `" const x = 1;"`;
+
+exports[` > with 'Windows' line endings > inserts a single space between paragraphs 1`] = `
+"Paragraph 1.
+
+Paragraph 2."
+`;
+
+exports[` > with 'Windows' line endings > renders a fenced code block with a language 1`] = `
+" 1 const x = 1;
+ 2 console.log(x);"
+`;
+
+exports[` > with 'Windows' line endings > renders a fenced code block without a language 1`] = `" 1 plain text"`;
+
+exports[` > with 'Windows' line endings > renders headers with correct levels 1`] = `
+"Header 1
+Header 2
+Header 3
+Header 4
+"
+`;
+
+exports[` > with 'Windows' line endings > renders horizontal rules 1`] = `
+"Hello
+---
+World
+---
+Test
+"
+`;
+
+exports[` > with 'Windows' line endings > renders nested unordered lists 1`] = `
+" * Level 1
+ * Level 2
+ * Level 3
+"
+`;
+
+exports[` > with 'Windows' line endings > renders ordered lists 1`] = `
+" 1. First item
+ 2. Second item
+"
+`;
+
+exports[` > with 'Windows' line endings > renders tables correctly 1`] = `
+"
+┌──────────┬──────────┐
+│ Header 1 │ Header 2 │
+├──────────┼──────────┤
+│ Cell 1 │ Cell 2 │
+│ Cell 3 │ Cell 4 │
+└──────────┴──────────┘
+"
+`;
+
+exports[` > with 'Windows' line endings > renders unordered lists with different markers 1`] = `
+" - item A
+ * item B
+ + item C
+"
+`;
+
+exports[` > with 'Windows' line endings > shows line numbers in code blocks by default 1`] = `" 1 const x = 1;"`;