1
1
import { Parent , Root , RootContent } from 'mdast'
2
2
import { remark } from 'remark'
3
3
import remarkStringify from 'remark-stringify'
4
+ import { Options } from 'remark-stringify'
4
5
5
- /**
6
- * Custom stringification of AST to preserve special patterns
7
- * @param ast AST to stringify
8
- * @returns Plain string representation
9
- */
10
- export function customAstToString ( ast : Root ) : string {
11
- let result = ''
12
- for ( const node of ast . children ) {
13
- result += nodeToString ( node ) + '\n'
14
- }
15
- return result . trim ( )
6
+ const REMARK_STRINGIFY_OPTIONS : Options = {
7
+ bullet : '*' ,
8
+ emphasis : '*' ,
9
+ fences : true ,
10
+ listItemIndent : 'one' ,
11
+ tightDefinitions : true
16
12
}
17
13
18
- /**
19
- * Convert a single node to string
20
- * @param node AST node
21
- * @returns String representation
22
- */
23
- function nodeToString ( node : any ) : string {
24
- switch ( node . type ) {
25
- case 'paragraph' :
26
- return paragraphToString ( node )
27
- case 'text' :
28
- return node . value
29
- default :
30
- const processor = remark ( ) . use ( remarkStringify )
31
- return processor . stringify ( { type : 'root' , children : [ node ] } ) . trim ( )
32
- }
33
- }
34
14
35
- /**
36
- * Convert paragraph node to string
37
- * @param node Paragraph node
38
- * @returns String representation
39
- */
40
- function paragraphToString ( node : any ) : string {
41
- return childrenToString ( node )
15
+ function createRemarkProcessor ( ) {
16
+ return remark ( ) . use ( remarkStringify , REMARK_STRINGIFY_OPTIONS )
42
17
}
43
18
44
19
/**
45
- * Process children of a node and join them
46
- * @param node Parent node
47
- * @returns Combined string of all children
20
+ * Custom stringification of AST using remarkStringify
21
+ * @param ast AST to stringify
22
+ * @returns Plain string representation
48
23
*/
49
- function childrenToString ( node : any ) : string {
50
- if ( ! node . children || node . children . length === 0 ) {
51
- return ''
52
- }
53
- return node . children . map ( ( child : any ) => nodeToString ( child ) ) . join ( '' )
24
+ export function customAstToString ( ast : Root ) : string {
25
+ const processor = createRemarkProcessor ( )
26
+ return processor . stringify ( ast ) . trim ( )
54
27
}
55
28
56
29
export function processCodeBlocksWithLabel ( ast : Root ) : RootContent [ ] {
@@ -62,8 +35,7 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
62
35
if ( node . type === 'code' && node . meta ) {
63
36
node . meta ?. split ( ' ' ) . forEach ( item => {
64
37
const [ key , rawValue ] = item . split ( / = ( .+ ) / )
65
- const value = rawValue ?. replace ( / ^ [ ' " ] | [ ' " ] $ / g, '' ) || ''
66
- metas [ key ] = value
38
+ metas [ key ] = rawValue
67
39
} )
68
40
}
69
41
@@ -94,10 +66,11 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
94
66
case 'file' :
95
67
if ( metas [ 'object' ] ) {
96
68
try {
97
- const fileObject = JSON . parse (
98
- metas [ 'object' ] . replace ( / \\ " / g, '"' ) . replace ( / \\ / g, '/' )
99
- )
100
- finalCommandText = `[[file:${ JSON . stringify ( fileObject ) } ]]`
69
+ finalCommandText = formatPlaceholder ( 'file' , metas [ 'object' ] )
70
+ if ( ! finalCommandText ) {
71
+ shouldProcessNode = false
72
+ newChildren . push ( node )
73
+ }
101
74
} catch ( error ) {
102
75
shouldProcessNode = false
103
76
newChildren . push ( node )
@@ -107,10 +80,11 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
107
80
case 'symbol' :
108
81
if ( metas [ 'object' ] ) {
109
82
try {
110
- const symbolObject = JSON . parse (
111
- metas [ 'object' ] . replace ( / \\ " / g, '"' ) . replace ( / \\ / g, '/' )
112
- )
113
- finalCommandText = `[[symbol:${ JSON . stringify ( symbolObject ) } ]]`
83
+ finalCommandText = formatPlaceholder ( 'symbol' , metas [ 'object' ] )
84
+ if ( ! finalCommandText ) {
85
+ shouldProcessNode = false
86
+ newChildren . push ( node )
87
+ }
114
88
} catch ( error ) {
115
89
shouldProcessNode = false
116
90
newChildren . push ( node )
@@ -180,7 +154,7 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
180
154
}
181
155
182
156
export function processContextCommand ( input : string ) : string {
183
- const processor = remark ( )
157
+ const processor = createRemarkProcessor ( )
184
158
const ast = processor . parse ( input ) as Root
185
159
ast . children = processCodeBlocksWithLabel ( ast )
186
160
return customAstToString ( ast )
@@ -189,3 +163,82 @@ export function processContextCommand(input: string): string {
189
163
export function convertContextBlockToPlaceholder ( input : string ) : string {
190
164
return processContextCommand ( input )
191
165
}
166
+
167
+ /**
168
+ * Format an object into a markdown code block with proper metadata
169
+ * @param label The label for the code block (e.g., 'file', 'symbol')
170
+ * @param obj The object to format
171
+ * @param content The content to include in the code block
172
+ * @returns A formatted markdown code block string
173
+ */
174
+ export function formatObjectToMarkdownBlock (
175
+ label : string ,
176
+ obj : any ,
177
+ content : string
178
+ ) : string {
179
+ try {
180
+ // Convert the object to a JSON string
181
+ const objJSON = JSON . stringify ( obj )
182
+
183
+ let safeContent = content
184
+
185
+ if ( safeContent . includes ( '```' ) ) {
186
+ safeContent = safeContent . replace ( / ` ` ` / g, '` ` `' )
187
+ }
188
+
189
+ if ( safeContent . trimEnd ( ) . endsWith ( '`' ) ) {
190
+ safeContent = safeContent + ' '
191
+ }
192
+
193
+ const codeNode : Root = {
194
+ type : 'root' ,
195
+ children : [
196
+ {
197
+ type : 'code' ,
198
+ lang : 'context' ,
199
+ meta : `label=${ label } object=${ objJSON } ` ,
200
+ value : safeContent
201
+ } as RootContent
202
+ ]
203
+ }
204
+
205
+ const processor = createRemarkProcessor ( )
206
+
207
+ const res = '\n' + processor . stringify ( codeNode ) . trim ( ) + '\n'
208
+ console . log ( 'res' , res )
209
+ return res ;
210
+ } catch ( error ) {
211
+ console . error ( `Error formatting ${ label } to markdown block:` , error )
212
+ return `\n*Error formatting ${ label } *\n`
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Escape backslashes in a string to handle Windows filepath issues
218
+ * @param str The string to process
219
+ * @returns The string with properly escaped backslashes
220
+ */
221
+ export function escapeBackslashes ( str : string ) : string {
222
+ if ( ! str ) return ''
223
+ return str . replace ( / \\ / g, '\\\\' )
224
+ }
225
+
226
+ /**
227
+ * Format a placeholder with proper backslash escaping
228
+ * @param type The type of placeholder (e.g., 'file', 'symbol')
229
+ * @param objStr The string representation of the object
230
+ * @returns The formatted placeholder text
231
+ */
232
+ export function formatPlaceholder ( type : string , objStr : string ) : string {
233
+ if ( ! objStr ) return ''
234
+
235
+ try {
236
+ console . log ( 'objStr' , JSON . parse ( objStr ) )
237
+ // Escape backslashes to ensure they're preserved
238
+ const escapedStr = escapeBackslashes ( objStr )
239
+ return `[[${ type } :${ escapedStr } ]]`
240
+ } catch ( error ) {
241
+ console . error ( `Error formatting ${ type } placeholder:` , error )
242
+ return ''
243
+ }
244
+ }
0 commit comments