@@ -7,14 +7,36 @@ const QUOTE_TEST_RE = /['"]/
77const QUOTE_RE = / [ ' " ] / g
88const APOSTROPHE = '\u2019' /* ’ */
99
10- function replaceAt ( str , index , ch ) {
11- return str . slice ( 0 , index ) + ch + str . slice ( index + 1 )
10+ function addReplacement ( replacements , tokenIdx , pos , ch ) {
11+ if ( ! replacements [ tokenIdx ] ) {
12+ replacements [ tokenIdx ] = [ ]
13+ }
14+
15+ replacements [ tokenIdx ] . push ( { pos, ch } )
16+ }
17+
18+ function applyReplacements ( str , replacements ) {
19+ let result = ''
20+ let lastPos = 0
21+
22+ replacements . sort ( ( a , b ) => a . pos - b . pos )
23+
24+ for ( let i = 0 ; i < replacements . length ; i ++ ) {
25+ const replacement = replacements [ i ]
26+
27+ result += str . slice ( lastPos , replacement . pos ) + replacement . ch
28+ lastPos = replacement . pos + 1
29+ }
30+
31+ return result + str . slice ( lastPos )
1232}
1333
1434function process_inlines ( tokens , state ) {
1535 let j
1636
1737 const stack = [ ]
38+ // token index -> list of replacements in the original token content
39+ const replacements = { }
1840
1941 for ( let i = 0 ; i < tokens . length ; i ++ ) {
2042 const token = tokens [ i ]
@@ -28,9 +50,9 @@ function process_inlines (tokens, state) {
2850
2951 if ( token . type !== 'text' ) { continue }
3052
31- let text = token . content
53+ const text = token . content
3254 let pos = 0
33- let max = text . length
55+ const max = text . length
3456
3557 /* eslint no-labels:0,block-scoped-var:0 */
3658 OUTER:
@@ -122,7 +144,7 @@ function process_inlines (tokens, state) {
122144 if ( ! canOpen && ! canClose ) {
123145 // middle of word
124146 if ( isSingle ) {
125- token . content = replaceAt ( token . content , t . index , APOSTROPHE )
147+ addReplacement ( replacements , i , t . index , APOSTROPHE )
126148 }
127149 continue
128150 }
@@ -145,18 +167,8 @@ function process_inlines (tokens, state) {
145167 closeQuote = state . md . options . quotes [ 1 ]
146168 }
147169
148- // replace token.content *before* tokens[item.token].content,
149- // because, if they are pointing at the same token, replaceAt
150- // could mess up indices when quote length != 1
151- token . content = replaceAt ( token . content , t . index , closeQuote )
152- tokens [ item . token ] . content = replaceAt (
153- tokens [ item . token ] . content , item . pos , openQuote )
154-
155- pos += closeQuote . length - 1
156- if ( item . token === i ) { pos += openQuote . length - 1 }
157-
158- text = token . content
159- max = text . length
170+ addReplacement ( replacements , i , t . index , closeQuote )
171+ addReplacement ( replacements , item . token , item . pos , openQuote )
160172
161173 stack . length = j
162174 continue OUTER
@@ -172,10 +184,14 @@ function process_inlines (tokens, state) {
172184 level : thisLevel
173185 } )
174186 } else if ( canClose && isSingle ) {
175- token . content = replaceAt ( token . content , t . index , APOSTROPHE )
187+ addReplacement ( replacements , i , t . index , APOSTROPHE )
176188 }
177189 }
178190 }
191+
192+ Object . keys ( replacements ) . forEach ( function ( tokenIdx ) {
193+ tokens [ tokenIdx ] . content = applyReplacements ( tokens [ tokenIdx ] . content , replacements [ tokenIdx ] )
194+ } )
179195}
180196
181197export default function smartquotes ( state ) {
0 commit comments