Skip to content

Commit 7331c80

Browse files
authored
Merge pull request #6 from vizhub-core/streaming-non-code-lines
Add streaming parsing of non-code lines
2 parents a026cfc + b86e848 commit 7331c80

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,13 @@ Converts an array of file objects into a Markdown string using the Bold Format.
129129

130130
A streaming parser that processes Markdown content chunk by chunk, emitting callbacks when file names or code blocks are detected. This is particularly useful for real-time code editing views where users can see LLMs edit specific files as they generate content.
131131

132-
```typescript
133-
import { StreamingMarkdownParser, StreamingParserCallbacks } from "llm-code-format";
132+
````typescript
133+
import {
134+
StreamingMarkdownParser,
135+
StreamingParserCallbacks,
136+
} from "llm-code-format";
134137

135-
// Define callbacks for file name changes and code lines
138+
// Define callbacks for file name changes, code lines, and non-code lines
136139
const callbacks: StreamingParserCallbacks = {
137140
onFileNameChange: (fileName, format) => {
138141
console.log(`File changed to: ${fileName} (${format})`);
@@ -141,7 +144,11 @@ const callbacks: StreamingParserCallbacks = {
141144
onCodeLine: (line) => {
142145
console.log(`Code line: ${line}`);
143146
// Append the line to the current file's content in the UI
144-
}
147+
},
148+
onNonCodeLine: (line) => {
149+
console.log(`Comment/text: ${line}`);
150+
// Process non-code, non-header lines (e.g., for displaying comments)
151+
},
145152
};
146153

147154
// Create a new parser instance with the callbacks
@@ -156,7 +163,7 @@ parser.processChunk("```\n");
156163

157164
// Flush any remaining content when the stream ends
158165
parser.flushRemaining();
159-
```
166+
````
160167

161168
#### Methods
162169

@@ -170,9 +177,14 @@ parser.flushRemaining();
170177
type StreamingParserCallbacks = {
171178
onFileNameChange: (fileName: string, format: string) => void;
172179
onCodeLine: (line: string) => void;
180+
onNonCodeLine?: (line: string) => void;
173181
};
174182
```
175183

184+
- **onFileNameChange**: Called when a file header is detected outside a code fence.
185+
- **onCodeLine**: Called for each line emitted from inside a code fence.
186+
- **onNonCodeLine**: Called for each line outside code fences that is not a file header.
187+
176188
Currently, the streaming parser only supports the "Bold Format" (`**filename.js**`), but is designed to be extensible for supporting additional formats in the future.
177189

178190
## License

src/streamingParser.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe("StreamingMarkdownParser", () => {
1010
format: string;
1111
}>;
1212
let codeLines: string[];
13+
let nonCodeLines: string[];
1314
let parser: StreamingMarkdownParser;
1415

1516
const callbacks: StreamingParserCallbacks = {
@@ -19,11 +20,15 @@ describe("StreamingMarkdownParser", () => {
1920
onCodeLine: (line) => {
2021
codeLines.push(line);
2122
},
23+
onNonCodeLine: (line) => {
24+
nonCodeLines.push(line);
25+
},
2226
};
2327

2428
beforeEach(() => {
2529
fileNameChanges = [];
2630
codeLines = [];
31+
nonCodeLines = [];
2732
parser = new StreamingMarkdownParser(callbacks);
2833
});
2934

@@ -86,13 +91,14 @@ describe("StreamingMarkdownParser", () => {
8691
]);
8792
});
8893

89-
it("should not trigger callbacks for irrelevant lines outside code fences", () => {
94+
it("should not trigger file name or code callbacks for irrelevant lines outside code fences", () => {
9095
// Provide a line that doesn't match any header or code fence
9196
parser.processChunk("This is an irrelevant line\n");
9297
parser.flushRemaining();
9398

9499
expect(fileNameChanges).toEqual([]);
95100
expect(codeLines).toEqual([]);
101+
expect(nonCodeLines).toEqual(["This is an irrelevant line"]);
96102
});
97103

98104
it("should handle nested chunks with header and code block boundaries", () => {
@@ -117,6 +123,7 @@ describe("StreamingMarkdownParser", () => {
117123
]);
118124

119125
expect(codeLines).toEqual(["<html>", "</html>", "body { color: blue; }"]);
126+
expect(nonCodeLines).toEqual(["Some irrelevant line"]);
120127
});
121128

122129
it("should handle bold format with extra text", () => {
@@ -130,4 +137,27 @@ describe("StreamingMarkdownParser", () => {
130137
]);
131138
expect(codeLines).toEqual(["<html>", "</html>"]);
132139
});
140+
it("should capture all non-code, non-header lines", () => {
141+
const input =
142+
"This is a regular text line\n" +
143+
"**index.html**\n" +
144+
"This is a comment about the file\n" +
145+
"```\n<html>\n</html>\n```\n" +
146+
"Another comment line\n" +
147+
"And one more line\n";
148+
149+
parser.processChunk(input);
150+
parser.flushRemaining();
151+
152+
expect(fileNameChanges).toEqual([
153+
{ name: "index.html", format: "Bold Format" },
154+
]);
155+
expect(codeLines).toEqual(["<html>", "</html>"]);
156+
expect(nonCodeLines).toEqual([
157+
"This is a regular text line",
158+
"This is a comment about the file",
159+
"Another comment line",
160+
"And one more line",
161+
]);
162+
});
133163
});

src/streamingParser.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export type StreamingParserCallbacks = {
1111
* @param line - A line of code from the code block.
1212
*/
1313
onCodeLine: (line: string) => void;
14+
15+
/**
16+
* Called for each line outside code fences that is not a file header.
17+
* @param line - A line of text that is not code or a file header.
18+
*/
19+
onNonCodeLine?: (line: string) => void;
1420
};
1521

1622
export class StreamingMarkdownParser {
@@ -96,7 +102,19 @@ export class StreamingMarkdownParser {
96102
break; // Stop after the first matching header is found.
97103
}
98104
}
99-
// Non-header lines outside code fences are ignored
105+
// Non-header lines outside code fences
106+
let isHeader = false;
107+
for (const { regex } of this.headerPatterns) {
108+
if (regex.test(line)) {
109+
isHeader = true;
110+
break;
111+
}
112+
}
113+
114+
// If it's not a header and the callback exists, call it
115+
if (!isHeader && this.callbacks.onNonCodeLine) {
116+
this.callbacks.onNonCodeLine(line);
117+
}
100118
}
101119
}
102120
}

0 commit comments

Comments
 (0)