code-text.js 5.4 KB

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