setext-underline.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /**
  2. * @typedef {import('micromark-util-types').Code} Code
  3. * @typedef {import('micromark-util-types').Construct} Construct
  4. * @typedef {import('micromark-util-types').Resolver} Resolver
  5. * @typedef {import('micromark-util-types').State} State
  6. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  7. * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
  8. */
  9. import {factorySpace} from 'micromark-factory-space'
  10. import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
  11. /** @type {Construct} */
  12. export const setextUnderline = {
  13. name: 'setextUnderline',
  14. tokenize: tokenizeSetextUnderline,
  15. resolveTo: resolveToSetextUnderline
  16. }
  17. /** @type {Resolver} */
  18. function resolveToSetextUnderline(events, context) {
  19. // To do: resolve like `markdown-rs`.
  20. let index = events.length
  21. /** @type {number | undefined} */
  22. let content
  23. /** @type {number | undefined} */
  24. let text
  25. /** @type {number | undefined} */
  26. let definition
  27. // Find the opening of the content.
  28. // It’ll always exist: we don’t tokenize if it isn’t there.
  29. while (index--) {
  30. if (events[index][0] === 'enter') {
  31. if (events[index][1].type === 'content') {
  32. content = index
  33. break
  34. }
  35. if (events[index][1].type === 'paragraph') {
  36. text = index
  37. }
  38. }
  39. // Exit
  40. else {
  41. if (events[index][1].type === 'content') {
  42. // Remove the content end (if needed we’ll add it later)
  43. events.splice(index, 1)
  44. }
  45. if (!definition && events[index][1].type === 'definition') {
  46. definition = index
  47. }
  48. }
  49. }
  50. const heading = {
  51. type: 'setextHeading',
  52. start: Object.assign({}, events[text][1].start),
  53. end: Object.assign({}, events[events.length - 1][1].end)
  54. }
  55. // Change the paragraph to setext heading text.
  56. events[text][1].type = 'setextHeadingText'
  57. // If we have definitions in the content, we’ll keep on having content,
  58. // but we need move it.
  59. if (definition) {
  60. events.splice(text, 0, ['enter', heading, context])
  61. events.splice(definition + 1, 0, ['exit', events[content][1], context])
  62. events[content][1].end = Object.assign({}, events[definition][1].end)
  63. } else {
  64. events[content][1] = heading
  65. }
  66. // Add the heading exit at the end.
  67. events.push(['exit', heading, context])
  68. return events
  69. }
  70. /**
  71. * @this {TokenizeContext}
  72. * @type {Tokenizer}
  73. */
  74. function tokenizeSetextUnderline(effects, ok, nok) {
  75. const self = this
  76. /** @type {NonNullable<Code>} */
  77. let marker
  78. return start
  79. /**
  80. * At start of heading (setext) underline.
  81. *
  82. * ```markdown
  83. * | aa
  84. * > | ==
  85. * ^
  86. * ```
  87. *
  88. * @type {State}
  89. */
  90. function start(code) {
  91. let index = self.events.length
  92. /** @type {boolean | undefined} */
  93. let paragraph
  94. // Find an opening.
  95. while (index--) {
  96. // Skip enter/exit of line ending, line prefix, and content.
  97. // We can now either have a definition or a paragraph.
  98. if (
  99. self.events[index][1].type !== 'lineEnding' &&
  100. self.events[index][1].type !== 'linePrefix' &&
  101. self.events[index][1].type !== 'content'
  102. ) {
  103. paragraph = self.events[index][1].type === 'paragraph'
  104. break
  105. }
  106. }
  107. // To do: handle lazy/pierce like `markdown-rs`.
  108. // To do: parse indent like `markdown-rs`.
  109. if (!self.parser.lazy[self.now().line] && (self.interrupt || paragraph)) {
  110. effects.enter('setextHeadingLine')
  111. marker = code
  112. return before(code)
  113. }
  114. return nok(code)
  115. }
  116. /**
  117. * After optional whitespace, at `-` or `=`.
  118. *
  119. * ```markdown
  120. * | aa
  121. * > | ==
  122. * ^
  123. * ```
  124. *
  125. * @type {State}
  126. */
  127. function before(code) {
  128. effects.enter('setextHeadingLineSequence')
  129. return inside(code)
  130. }
  131. /**
  132. * In sequence.
  133. *
  134. * ```markdown
  135. * | aa
  136. * > | ==
  137. * ^
  138. * ```
  139. *
  140. * @type {State}
  141. */
  142. function inside(code) {
  143. if (code === marker) {
  144. effects.consume(code)
  145. return inside
  146. }
  147. effects.exit('setextHeadingLineSequence')
  148. return markdownSpace(code)
  149. ? factorySpace(effects, after, 'lineSuffix')(code)
  150. : after(code)
  151. }
  152. /**
  153. * After sequence, after optional whitespace.
  154. *
  155. * ```markdown
  156. * | aa
  157. * > | ==
  158. * ^
  159. * ```
  160. *
  161. * @type {State}
  162. */
  163. function after(code) {
  164. if (code === null || markdownLineEnding(code)) {
  165. effects.exit('setextHeadingLine')
  166. return ok(code)
  167. }
  168. return nok(code)
  169. }
  170. }