code-text.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * @typedef {import('micromark-util-types').Construct} Construct
  3. * @typedef {import('micromark-util-types').Previous} Previous
  4. * @typedef {import('micromark-util-types').Resolver} Resolver
  5. * @typedef {import('micromark-util-types').State} State
  6. * @typedef {import('micromark-util-types').Token} Token
  7. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  8. * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
  9. */
  10. import {markdownLineEnding} from 'micromark-util-character'
  11. /** @type {Construct} */
  12. export const codeText = {
  13. name: 'codeText',
  14. tokenize: tokenizeCodeText,
  15. resolve: resolveCodeText,
  16. previous
  17. }
  18. // To do: next major: don’t resolve, like `markdown-rs`.
  19. /** @type {Resolver} */
  20. function resolveCodeText(events) {
  21. let tailExitIndex = events.length - 4
  22. let headEnterIndex = 3
  23. /** @type {number} */
  24. let index
  25. /** @type {number | undefined} */
  26. let enter
  27. // If we start and end with an EOL or a space.
  28. if (
  29. (events[headEnterIndex][1].type === 'lineEnding' ||
  30. events[headEnterIndex][1].type === 'space') &&
  31. (events[tailExitIndex][1].type === 'lineEnding' ||
  32. events[tailExitIndex][1].type === 'space')
  33. ) {
  34. index = headEnterIndex
  35. // And we have data.
  36. while (++index < tailExitIndex) {
  37. if (events[index][1].type === 'codeTextData') {
  38. // Then we have padding.
  39. events[headEnterIndex][1].type = 'codeTextPadding'
  40. events[tailExitIndex][1].type = 'codeTextPadding'
  41. headEnterIndex += 2
  42. tailExitIndex -= 2
  43. break
  44. }
  45. }
  46. }
  47. // Merge adjacent spaces and data.
  48. index = headEnterIndex - 1
  49. tailExitIndex++
  50. while (++index <= tailExitIndex) {
  51. if (enter === undefined) {
  52. if (index !== tailExitIndex && events[index][1].type !== 'lineEnding') {
  53. enter = index
  54. }
  55. } else if (
  56. index === tailExitIndex ||
  57. events[index][1].type === 'lineEnding'
  58. ) {
  59. events[enter][1].type = 'codeTextData'
  60. if (index !== enter + 2) {
  61. events[enter][1].end = events[index - 1][1].end
  62. events.splice(enter + 2, index - enter - 2)
  63. tailExitIndex -= index - enter - 2
  64. index = enter + 2
  65. }
  66. enter = undefined
  67. }
  68. }
  69. return events
  70. }
  71. /**
  72. * @this {TokenizeContext}
  73. * @type {Previous}
  74. */
  75. function previous(code) {
  76. // If there is a previous code, there will always be a tail.
  77. return (
  78. code !== 96 ||
  79. this.events[this.events.length - 1][1].type === 'characterEscape'
  80. )
  81. }
  82. /**
  83. * @this {TokenizeContext}
  84. * @type {Tokenizer}
  85. */
  86. function tokenizeCodeText(effects, ok, nok) {
  87. const self = this
  88. let sizeOpen = 0
  89. /** @type {number} */
  90. let size
  91. /** @type {Token} */
  92. let token
  93. return start
  94. /**
  95. * Start of code (text).
  96. *
  97. * ```markdown
  98. * > | `a`
  99. * ^
  100. * > | \`a`
  101. * ^
  102. * ```
  103. *
  104. * @type {State}
  105. */
  106. function start(code) {
  107. effects.enter('codeText')
  108. effects.enter('codeTextSequence')
  109. return sequenceOpen(code)
  110. }
  111. /**
  112. * In opening sequence.
  113. *
  114. * ```markdown
  115. * > | `a`
  116. * ^
  117. * ```
  118. *
  119. * @type {State}
  120. */
  121. function sequenceOpen(code) {
  122. if (code === 96) {
  123. effects.consume(code)
  124. sizeOpen++
  125. return sequenceOpen
  126. }
  127. effects.exit('codeTextSequence')
  128. return between(code)
  129. }
  130. /**
  131. * Between something and something else.
  132. *
  133. * ```markdown
  134. * > | `a`
  135. * ^^
  136. * ```
  137. *
  138. * @type {State}
  139. */
  140. function between(code) {
  141. // EOF.
  142. if (code === null) {
  143. return nok(code)
  144. }
  145. // To do: next major: don’t do spaces in resolve, but when compiling,
  146. // like `markdown-rs`.
  147. // Tabs don’t work, and virtual spaces don’t make sense.
  148. if (code === 32) {
  149. effects.enter('space')
  150. effects.consume(code)
  151. effects.exit('space')
  152. return between
  153. }
  154. // Closing fence? Could also be data.
  155. if (code === 96) {
  156. token = effects.enter('codeTextSequence')
  157. size = 0
  158. return sequenceClose(code)
  159. }
  160. if (markdownLineEnding(code)) {
  161. effects.enter('lineEnding')
  162. effects.consume(code)
  163. effects.exit('lineEnding')
  164. return between
  165. }
  166. // Data.
  167. effects.enter('codeTextData')
  168. return data(code)
  169. }
  170. /**
  171. * In data.
  172. *
  173. * ```markdown
  174. * > | `a`
  175. * ^
  176. * ```
  177. *
  178. * @type {State}
  179. */
  180. function data(code) {
  181. if (
  182. code === null ||
  183. code === 32 ||
  184. code === 96 ||
  185. markdownLineEnding(code)
  186. ) {
  187. effects.exit('codeTextData')
  188. return between(code)
  189. }
  190. effects.consume(code)
  191. return data
  192. }
  193. /**
  194. * In closing sequence.
  195. *
  196. * ```markdown
  197. * > | `a`
  198. * ^
  199. * ```
  200. *
  201. * @type {State}
  202. */
  203. function sequenceClose(code) {
  204. // More.
  205. if (code === 96) {
  206. effects.consume(code)
  207. size++
  208. return sequenceClose
  209. }
  210. // Done!
  211. if (size === sizeOpen) {
  212. effects.exit('codeTextSequence')
  213. effects.exit('codeText')
  214. return ok(code)
  215. }
  216. // More or less accents: mark as data.
  217. token.type = 'codeTextData'
  218. return data(code)
  219. }
  220. }