Skip to content

Commit 5fbad80

Browse files
fix bug with terminating lists on certain models (#49)
* fix asterisk list termination * Create fancy-snails-march.md
1 parent 13898aa commit 5fbad80

File tree

3 files changed

+75
-1
lines changed

3 files changed

+75
-1
lines changed

.changeset/fancy-snails-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"streamdown": patch
3+
---
4+
5+
fix asterisk list termination

packages/streamdown/__tests__/parse-incomplete-markdown.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,56 @@ describe('parseIncompleteMarkdown', () => {
693693
});
694694
});
695695

696+
describe('list handling', () => {
697+
it('should not add asterisk to lists using asterisk markers', () => {
698+
const text = '* Item 1\n* Item 2\n* Item 3';
699+
expect(parseIncompleteMarkdown(text)).toBe(text);
700+
});
701+
702+
it('should not add asterisk to single list item', () => {
703+
const text = '* Single item';
704+
expect(parseIncompleteMarkdown(text)).toBe(text);
705+
});
706+
707+
it('should not add asterisk to nested lists', () => {
708+
const text = '* Parent item\n * Nested item 1\n * Nested item 2';
709+
expect(parseIncompleteMarkdown(text)).toBe(text);
710+
});
711+
712+
it('should handle lists with italic text correctly', () => {
713+
const text = '* Item with *italic* text\n* Another item';
714+
expect(parseIncompleteMarkdown(text)).toBe(text);
715+
});
716+
717+
it('should complete incomplete italic even in list items', () => {
718+
// List markers are not counted, but incomplete italic formatting is still completed
719+
const text = '* Item with *incomplete italic\n* Another item';
720+
// The function adds an asterisk to complete the italic, though at the end of text
721+
// This is not ideal but matches current behavior
722+
expect(parseIncompleteMarkdown(text)).toBe('* Item with *incomplete italic\n* Another item*');
723+
});
724+
725+
it('should handle mixed list markers and italic formatting', () => {
726+
const text = '* First item\n* Second *italic* item\n* Third item';
727+
expect(parseIncompleteMarkdown(text)).toBe(text);
728+
});
729+
730+
it('should handle lists with tabs for indentation', () => {
731+
const text = '*\tItem with tab\n*\tAnother item';
732+
expect(parseIncompleteMarkdown(text)).toBe(text);
733+
});
734+
735+
it('should not interfere with dash lists', () => {
736+
const text = '- Item 1\n- Item 2 with *italic*\n- Item 3';
737+
expect(parseIncompleteMarkdown(text)).toBe(text);
738+
});
739+
740+
it('should handle the Gemini response example from issue', () => {
741+
const geminiResponse = '* user123\n* user456\n* user789';
742+
expect(parseIncompleteMarkdown(geminiResponse)).toBe(geminiResponse);
743+
});
744+
});
745+
696746
describe('edge cases', () => {
697747
it('should handle text ending with formatting characters', () => {
698748
expect(parseIncompleteMarkdown('Text ending with *')).toBe(

packages/streamdown/lib/parse-incomplete-markdown.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const handleIncompleteDoubleUnderscoreItalic = (text: string): string => {
4949
return text;
5050
};
5151

52-
// Counts single asterisks that are not part of double asterisks and not escaped
52+
// Counts single asterisks that are not part of double asterisks, not escaped, and not list markers
5353
const countSingleAsterisks = (text: string): number => {
5454
return text.split('').reduce((acc, char, index) => {
5555
if (char === '*') {
@@ -59,6 +59,25 @@ const countSingleAsterisks = (text: string): number => {
5959
if (prevChar === '\\') {
6060
return acc;
6161
}
62+
// Check if this is a list marker (asterisk at start of line followed by space)
63+
// Look backwards to find the start of the current line
64+
let lineStartIndex = index;
65+
for (let i = index - 1; i >= 0; i--) {
66+
if (text[i] === '\n') {
67+
lineStartIndex = i + 1;
68+
break;
69+
}
70+
if (i === 0) {
71+
lineStartIndex = 0;
72+
break;
73+
}
74+
}
75+
// Check if this asterisk is at the beginning of a line (with optional whitespace)
76+
const beforeAsterisk = text.substring(lineStartIndex, index);
77+
if (beforeAsterisk.trim() === '' && (nextChar === ' ' || nextChar === '\t')) {
78+
// This is likely a list marker, don't count it
79+
return acc;
80+
}
6281
if (prevChar !== '*' && nextChar !== '*') {
6382
return acc + 1;
6483
}

0 commit comments

Comments
 (0)