@@ -24,6 +24,11 @@ type PositionPlusEmptyIndicator = {
2424 isEmpty : boolean ,
2525}
2626
27+ type PositionPlusText = {
28+ position : Position ,
29+ text : string ,
30+ }
31+
2732export enum MDAstTypes {
2833 Link = 'link' ,
2934 Footnote = 'footnoteDefinition' ,
@@ -38,6 +43,8 @@ export enum MDAstTypes {
3843 Blockquote = 'blockquote' ,
3944 HorizontalRule = 'thematicBreak' ,
4045 Html = 'html' ,
46+ Heading = 'heading' ,
47+ Text = 'text' ,
4148 // math types
4249 Math = 'math' ,
4350 InlineMath = 'inlineMath' ,
@@ -149,6 +156,31 @@ function getListItemTextPositions(text: string, includeEmptyNodes: boolean = fal
149156 return positions ;
150157}
151158
159+ function getHeaderTextPositions ( text : string ) : PositionPlusText [ ] {
160+ const ast = parseTextToAST ( text ) ;
161+ const positions : PositionPlusText [ ] = [ ] ;
162+ visit ( ast , MDAstTypes . Heading as string , ( node ) => {
163+ // @ts -ignore the fact that not all nodes have a children property since I am skipping any that do not
164+ if ( ! node . children || node . children . length === 0 ) {
165+ return ;
166+ }
167+
168+ // @ts -ignore the fact that not all nodes have a children property since I have already exited the function if that is the case
169+ for ( const childNode of node . children ) {
170+ if ( childNode . type === ( MDAstTypes . Text as string ) ) {
171+ positions . push ( {
172+ position : childNode . position as Position ,
173+ text : childNode . value as string ,
174+ } ) ;
175+ }
176+ }
177+ } ) ;
178+
179+ // Sort positions by start position in reverse order
180+ positions . sort ( ( a , b ) => b . position . start . offset - a . position . start . offset ) ;
181+ return positions ;
182+ }
183+
152184// mdast helper methods
153185
154186/**
@@ -1169,3 +1201,30 @@ export function ensureFencedCodeBlocksHasLanguage(text: string, defaultLanguage:
11691201
11701202 return text ;
11711203}
1204+
1205+ export function updateHeaderText ( text : string , func :( text : string ) => string ) : string {
1206+ const positions = getHeaderTextPositions ( text ) ;
1207+
1208+ // for the best performance, we want to grab all places that need updating and then
1209+ // at the end we want to update the text in one go because otherwise we get a lot of
1210+ // instances of the file text in memory
1211+ const updateLocations : { startIndex : number , endIndex : number , newText : string } [ ] = [ ] ;
1212+ for ( const position of positions ) {
1213+ const updatedText = func ( position . text ) ;
1214+ if ( updatedText !== position . text ) {
1215+ const headerText = text . substring ( position . position . start . offset , position . position . end . offset ) ;
1216+ const startIndex = position . position . start . offset + headerText . indexOf ( position . text ) ;
1217+ updateLocations . push ( {
1218+ startIndex : startIndex ,
1219+ endIndex : startIndex + position . text . length ,
1220+ newText : updatedText ,
1221+ } ) ;
1222+ }
1223+ }
1224+
1225+ for ( const headerUpdate of updateLocations ) {
1226+ text = replaceTextBetweenStartAndEndWithNewValue ( text , headerUpdate . startIndex , headerUpdate . endIndex , headerUpdate . newText ) ;
1227+ }
1228+
1229+ return text ;
1230+ }
0 commit comments