@@ -3,6 +3,17 @@ import { distance } from "fastest-levenshtein";
3
3
import { DiffLine } from "../../.." ;
4
4
import { LineStream } from "../../../diff/util" ;
5
5
6
+ import {
7
+ headerIsMarkdown ,
8
+ isMarkdownFile ,
9
+ MarkdownBlockStateTracker ,
10
+ collectAllLines ,
11
+ } from "../../../utils/markdownUtils" ;
12
+ import {
13
+ shouldStopAtMarkdownBlock ,
14
+ processBlockNesting as processBlockNestingUtil ,
15
+ } from "../../../utils/streamMarkdownUtils" ;
16
+
6
17
export { filterCodeBlockLines } from "./filterCodeBlock" ;
7
18
8
19
export type LineFilter = ( args : {
@@ -18,23 +29,6 @@ export type CharacterFilter = (args: {
18
29
multiline : boolean ;
19
30
} ) => AsyncGenerator < string > ;
20
31
21
- // Duplicated from gui/src/components/StyledMarkdownPreview/utils/headerIsMarkdown.ts
22
- function headerIsMarkdown ( header : string ) : boolean {
23
- return (
24
- header === "md" ||
25
- header === "markdown" ||
26
- header === "gfm" ||
27
- header === "github-markdown" ||
28
- header . includes ( " md" ) ||
29
- header . includes ( " markdown" ) ||
30
- header . includes ( " gfm" ) ||
31
- header . includes ( " github-markdown" ) ||
32
- header . split ( " " ) [ 0 ] ?. split ( "." ) . pop ( ) === "md" ||
33
- header . split ( " " ) [ 0 ] ?. split ( "." ) . pop ( ) === "markdown" ||
34
- header . split ( " " ) [ 0 ] ?. split ( "." ) . pop ( ) === "gfm"
35
- ) ;
36
- }
37
-
38
32
function isBracketEnding ( line : string ) : boolean {
39
33
return line
40
34
. trim ( )
@@ -124,19 +118,6 @@ function isUselessLine(line: string): boolean {
124
118
return hasUselessLine || trimmed . startsWith ( "// end" ) ;
125
119
}
126
120
127
- /**
128
- * Determines if a file is a markdown file based on its filepath.
129
- */
130
- export function isMarkdownFile ( filepath ?: string ) : boolean {
131
- if ( ! filepath ) {
132
- return false ;
133
- }
134
-
135
- return [ "md" , "markdown" , "gfm" ] . includes (
136
- filepath . split ( "." ) . pop ( ) ?. toLowerCase ( ) || "" ,
137
- ) ;
138
- }
139
-
140
121
/**
141
122
* Determines if the code block has nested markdown blocks.
142
123
*/
@@ -147,116 +128,25 @@ export function hasNestedMarkdownBlocks(
147
128
return (
148
129
( firstLine . startsWith ( "```" ) &&
149
130
headerIsMarkdown ( firstLine . replace ( / ` / g, "" ) ) ) ||
150
- Boolean ( filepath && headerIsMarkdown ( filepath ) )
131
+ Boolean ( filepath && isMarkdownFile ( filepath ) )
151
132
) ;
152
133
}
153
134
154
- /**
155
- * Collects all lines from a LineStream into an array for analysis.
156
- */
157
- export async function collectAllLines ( rawLines : LineStream ) : Promise < string [ ] > {
158
- const allLines : string [ ] = [ ] ;
159
- for await ( const line of rawLines ) {
160
- allLines . push ( line ) ;
161
- }
162
- return allLines ;
163
- }
164
-
165
- /**
166
- * State tracker for markdown block analysis to avoid recomputing on each call.
167
- */
168
- export class MarkdownBlockState {
169
- private trimmedLines : string [ ] ;
170
- private bareBacktickPositions : number [ ] ;
171
- private markdownNestCount : number = 0 ;
172
- private lastProcessedIndex : number = - 1 ;
173
-
174
- constructor ( allLines : string [ ] ) {
175
- this . trimmedLines = allLines . map ( ( l ) => l . trim ( ) ) ;
176
- // Pre-compute positions of all bare backtick lines for faster lookup
177
- this . bareBacktickPositions = [ ] ;
178
- for ( let i = 0 ; i < this . trimmedLines . length ; i ++ ) {
179
- if ( this . trimmedLines [ i ] . match ( / ^ ` + $ / ) ) {
180
- this . bareBacktickPositions . push ( i ) ;
181
- }
182
- }
183
- }
184
-
185
- /**
186
- * Determines if we should stop at the given markdown block position.
187
- * Maintains state across calls to avoid redundant computation.
188
- */
189
- shouldStopAtPosition ( currentIndex : number ) : boolean {
190
- if ( this . trimmedLines [ currentIndex ] !== "```" ) {
191
- return false ;
192
- }
193
-
194
- // Process any lines we haven't seen yet up to currentIndex
195
- for ( let j = this . lastProcessedIndex + 1 ; j <= currentIndex ; j ++ ) {
196
- const currentLine = this . trimmedLines [ j ] ;
197
-
198
- if ( this . markdownNestCount > 0 ) {
199
- // Inside a markdown block
200
- if ( currentLine . match ( / ^ ` + $ / ) ) {
201
- // Found bare backticks - check if this is the last one
202
- if ( j === currentIndex ) {
203
- const remainingBareBackticks = this . bareBacktickPositions . filter (
204
- ( pos ) => pos > j ,
205
- ) . length ;
206
- if ( remainingBareBackticks === 0 ) {
207
- this . markdownNestCount = 0 ;
208
- this . lastProcessedIndex = j ;
209
- return true ;
210
- }
211
- }
212
- } else if ( currentLine . startsWith ( "```" ) ) {
213
- // Going into a nested codeblock
214
- this . markdownNestCount ++ ;
215
- }
216
- } else {
217
- // Not inside a markdown block yet
218
- if ( currentLine . startsWith ( "```" ) ) {
219
- const header = currentLine . replaceAll ( "`" , "" ) ;
220
- if ( headerIsMarkdown ( header ) ) {
221
- this . markdownNestCount = 1 ;
222
- }
223
- }
224
- }
225
- }
226
-
227
- this . lastProcessedIndex = currentIndex ;
228
- return false ;
229
- }
230
- }
231
-
232
- /**
233
- * Determines if we should stop at a markdown block based on nested markdown logic.
234
- * This handles the complex case where markdown blocks contain other markdown blocks.
235
- * Uses optimized state tracking to avoid redundant computation.
236
- */
237
- export function shouldStopAtMarkdownBlock (
238
- stateTracker : MarkdownBlockState ,
239
- currentIndex : number ,
240
- ) : boolean {
241
- return stateTracker . shouldStopAtPosition ( currentIndex ) ;
242
- }
135
+ // Re-export shared utilities
136
+ export { collectAllLines , isMarkdownFile } ;
137
+ export { MarkdownBlockStateTracker as MarkdownBlockState } ;
138
+ export { shouldStopAtMarkdownBlock } ;
243
139
244
- /**
245
- * Processes block nesting logic and returns updated state.
246
- */
140
+ // Wrapper for processBlockNesting with local shouldRemoveLineBeforeStart function
247
141
export function processBlockNesting (
248
142
line : string ,
249
143
seenFirstFence : boolean ,
250
144
) : { newSeenFirstFence : boolean ; shouldSkip : boolean } {
251
- if ( ! seenFirstFence && shouldRemoveLineBeforeStart ( line ) ) {
252
- return { newSeenFirstFence : false , shouldSkip : true } ;
253
- }
254
-
255
- if ( ! seenFirstFence ) {
256
- return { newSeenFirstFence : true , shouldSkip : false } ;
257
- }
258
-
259
- return { newSeenFirstFence : seenFirstFence , shouldSkip : false } ;
145
+ return processBlockNestingUtil (
146
+ line ,
147
+ seenFirstFence ,
148
+ shouldRemoveLineBeforeStart ,
149
+ ) ;
260
150
}
261
151
262
152
export const USELESS_LINES = [ "" ] ;
@@ -326,7 +216,7 @@ export async function* avoidPathLine(
326
216
// Sometimes the model with copy this pattern, which is unwanted
327
217
for await ( const line of stream ) {
328
218
if ( line . startsWith ( `${ comment } Path: ` ) ) {
329
- continue ;
219
+ continue ; // continue in the Continue codebase! How meta!
330
220
}
331
221
yield line ;
332
222
}
0 commit comments