1
1
import { Parent , Root , RootContent } from 'mdast'
2
2
import { remark } from 'remark'
3
- import remarkStringify from 'remark-stringify'
3
+ import remarkStringify , { Options } from 'remark-stringify'
4
4
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 ( )
5
+ const REMARK_STRINGIFY_OPTIONS : Options = {
6
+ bullet : '*' ,
7
+ emphasis : '*' ,
8
+ fences : true ,
9
+ listItemIndent : 'one' ,
10
+ tightDefinitions : true ,
11
+ handlers : {
12
+ placeholder : ( node : PlaceholderNode ) => {
13
+ return node . value
14
+ }
15
+ } as any
16
16
}
17
17
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
- }
18
+ function createRemarkProcessor ( ) {
19
+ return remark ( ) . use ( remarkStringify , REMARK_STRINGIFY_OPTIONS )
33
20
}
34
21
35
22
/**
36
- * Convert paragraph node to string
37
- * @param node Paragraph node
38
- * @returns String representation
23
+ * Custom stringification of AST using remarkStringify
24
+ * @param ast AST to stringify
25
+ * @returns Plain string representation
39
26
*/
40
- function paragraphToString ( node : any ) : string {
41
- return childrenToString ( node )
27
+ export function customAstToString ( ast : Root ) : string {
28
+ const processor = createRemarkProcessor ( )
29
+ return processor . stringify ( ast ) . trim ( )
42
30
}
43
31
44
32
/**
45
- * Process children of a node and join them
46
- * @param node Parent node
47
- * @returns Combined string of all children
33
+ * Process code blocks with labels and convert them to placeholders
48
34
*/
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 ( '' )
54
- }
55
-
56
35
export function processCodeBlocksWithLabel ( ast : Root ) : RootContent [ ] {
57
36
const newChildren : RootContent [ ] = [ ]
58
37
for ( let i = 0 ; i < ast . children . length ; i ++ ) {
@@ -62,8 +41,7 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
62
41
if ( node . type === 'code' && node . meta ) {
63
42
node . meta ?. split ( ' ' ) . forEach ( item => {
64
43
const [ key , rawValue ] = item . split ( / = ( .+ ) / )
65
- const value = rawValue ?. replace ( / ^ [ ' " ] | [ ' " ] $ / g, '' ) || ''
66
- metas [ key ] = value
44
+ metas [ key ] = rawValue
67
45
} )
68
46
}
69
47
@@ -84,31 +62,57 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
84
62
nextNode . position . start . line - node . position . end . line === 1
85
63
86
64
let finalCommandText = ''
65
+ let placeholderNode : RootContent | null = null
66
+ let shouldProcessNode = true
87
67
88
- // processing differet type of context
89
68
switch ( metas [ 'label' ] ) {
90
69
case 'changes' :
91
- finalCommandText = '[[contextCommand:"changes"]]'
70
+ finalCommandText = '"changes"'
71
+ placeholderNode = createPlaceholderNode (
72
+ `[[contextCommand:${ finalCommandText } ]]`
73
+ ) as unknown as RootContent
92
74
break
93
75
case 'file' :
94
76
if ( metas [ 'object' ] ) {
95
- const fileObject = JSON . parse ( metas [ 'object' ] . replace ( / \\ " / g, '"' ) )
96
- finalCommandText = `[[file:${ JSON . stringify ( fileObject ) } ]]`
77
+ try {
78
+ placeholderNode = createPlaceholderNode (
79
+ `[[file:${ metas [ 'object' ] } ]]`
80
+ ) as unknown as RootContent
81
+ if ( ! placeholderNode ) {
82
+ shouldProcessNode = false
83
+ newChildren . push ( node )
84
+ }
85
+ } catch ( error ) {
86
+ shouldProcessNode = false
87
+ newChildren . push ( node )
88
+ }
97
89
}
98
90
break
99
91
case 'symbol' :
100
92
if ( metas [ 'object' ] ) {
101
- const symbolObject = JSON . parse (
102
- metas [ 'object' ] . replace ( / \\ " / g, '"' )
103
- )
104
- finalCommandText = `[[symbol:${ JSON . stringify ( symbolObject ) } ]]`
93
+ try {
94
+ placeholderNode = createPlaceholderNode (
95
+ `[[symbol:${ metas [ 'object' ] } ]]`
96
+ ) as unknown as RootContent
97
+ if ( ! placeholderNode ) {
98
+ shouldProcessNode = false
99
+ newChildren . push ( node )
100
+ }
101
+ } catch ( error ) {
102
+ shouldProcessNode = false
103
+ newChildren . push ( node )
104
+ }
105
105
}
106
106
break
107
107
default :
108
108
newChildren . push ( node )
109
109
continue
110
110
}
111
111
112
+ if ( ! shouldProcessNode ) {
113
+ continue
114
+ }
115
+
112
116
if (
113
117
prevNode &&
114
118
prevNode . type === 'paragraph' &&
@@ -123,7 +127,7 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
123
127
type : 'paragraph' ,
124
128
children : [
125
129
...( prevNode . children || [ ] ) ,
126
- { type : 'text' , value : ` ${ finalCommandText } ` } ,
130
+ placeholderNode || { type : 'text' , value : ` ${ finalCommandText } ` } ,
127
131
...( nextNode . children || [ ] )
128
132
]
129
133
} as RootContent )
@@ -136,7 +140,7 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
136
140
newChildren . push ( {
137
141
type : 'paragraph' ,
138
142
children : [
139
- { type : 'text' , value : `${ finalCommandText } ` } ,
143
+ placeholderNode || { type : 'text' , value : `${ finalCommandText } ` } ,
140
144
...( nextNode . children || [ ] )
141
145
]
142
146
} as RootContent )
@@ -145,14 +149,15 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
145
149
prevNode . type === 'paragraph' &&
146
150
isPrevNodeSameLine
147
151
) {
148
- ; ( prevNode . children || [ ] ) . push ( {
149
- type : 'text' ,
150
- value : ` ${ finalCommandText } `
151
- } )
152
+ ; ( prevNode . children || [ ] ) . push (
153
+ placeholderNode || { type : 'text' , value : ` ${ finalCommandText } ` }
154
+ )
152
155
} else {
153
156
newChildren . push ( {
154
157
type : 'paragraph' ,
155
- children : [ { type : 'text' , value : finalCommandText } ]
158
+ children : [
159
+ placeholderNode || { type : 'text' , value : finalCommandText }
160
+ ]
156
161
} as RootContent )
157
162
}
158
163
} else {
@@ -163,7 +168,7 @@ export function processCodeBlocksWithLabel(ast: Root): RootContent[] {
163
168
}
164
169
165
170
export function processContextCommand ( input : string ) : string {
166
- const processor = remark ( )
171
+ const processor = createRemarkProcessor ( )
167
172
const ast = processor . parse ( input ) as Root
168
173
ast . children = processCodeBlocksWithLabel ( ast )
169
174
return customAstToString ( ast )
@@ -172,3 +177,51 @@ export function processContextCommand(input: string): string {
172
177
export function convertContextBlockToPlaceholder ( input : string ) : string {
173
178
return processContextCommand ( input )
174
179
}
180
+
181
+ /**
182
+ * Format an object into a markdown code block with proper metadata
183
+ * @param label The label for the code block (e.g., 'file', 'symbol')
184
+ * @param obj The object to format
185
+ * @param content The content to include in the code block
186
+ * @returns A formatted markdown code block string
187
+ */
188
+ export function formatObjectToMarkdownBlock (
189
+ label : string ,
190
+ obj : any ,
191
+ content : string
192
+ ) : string {
193
+ try {
194
+ const objJSON = JSON . stringify ( obj )
195
+
196
+ const codeNode : Root = {
197
+ type : 'root' ,
198
+ children : [
199
+ {
200
+ type : 'code' ,
201
+ lang : 'context' ,
202
+ meta : `label=${ label } object=${ objJSON } ` ,
203
+ value : content
204
+ } as RootContent
205
+ ]
206
+ }
207
+
208
+ const processor = createRemarkProcessor ( )
209
+
210
+ const res = '\n' + processor . stringify ( codeNode ) . trim ( ) + '\n'
211
+ return res
212
+ } catch ( error ) {
213
+ return `\n*Error formatting ${ label } *\n`
214
+ }
215
+ }
216
+
217
+ export interface PlaceholderNode extends Node {
218
+ type : 'placeholder'
219
+ value : string
220
+ }
221
+
222
+ export function createPlaceholderNode ( value : string ) : PlaceholderNode {
223
+ return {
224
+ type : 'placeholder' ,
225
+ value : value
226
+ } as PlaceholderNode
227
+ }
0 commit comments