code-indented.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /**
  2. * @typedef {import('micromark-util-types').Construct} Construct
  3. * @typedef {import('micromark-util-types').State} State
  4. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  5. * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
  6. */
  7. import {factorySpace} from 'micromark-factory-space'
  8. import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
  9. /** @type {Construct} */
  10. export const codeIndented = {
  11. name: 'codeIndented',
  12. tokenize: tokenizeCodeIndented
  13. }
  14. /** @type {Construct} */
  15. const furtherStart = {
  16. tokenize: tokenizeFurtherStart,
  17. partial: true
  18. }
  19. /**
  20. * @this {TokenizeContext}
  21. * @type {Tokenizer}
  22. */
  23. function tokenizeCodeIndented(effects, ok, nok) {
  24. const self = this
  25. return start
  26. /**
  27. * Start of code (indented).
  28. *
  29. * > **Parsing note**: it is not needed to check if this first line is a
  30. * > filled line (that it has a non-whitespace character), because blank lines
  31. * > are parsed already, so we never run into that.
  32. *
  33. * ```markdown
  34. * > | aaa
  35. * ^
  36. * ```
  37. *
  38. * @type {State}
  39. */
  40. function start(code) {
  41. // To do: manually check if interrupting like `markdown-rs`.
  42. effects.enter('codeIndented')
  43. // To do: use an improved `space_or_tab` function like `markdown-rs`,
  44. // so that we can drop the next state.
  45. return factorySpace(effects, afterPrefix, 'linePrefix', 4 + 1)(code)
  46. }
  47. /**
  48. * At start, after 1 or 4 spaces.
  49. *
  50. * ```markdown
  51. * > | aaa
  52. * ^
  53. * ```
  54. *
  55. * @type {State}
  56. */
  57. function afterPrefix(code) {
  58. const tail = self.events[self.events.length - 1]
  59. return tail &&
  60. tail[1].type === 'linePrefix' &&
  61. tail[2].sliceSerialize(tail[1], true).length >= 4
  62. ? atBreak(code)
  63. : nok(code)
  64. }
  65. /**
  66. * At a break.
  67. *
  68. * ```markdown
  69. * > | aaa
  70. * ^ ^
  71. * ```
  72. *
  73. * @type {State}
  74. */
  75. function atBreak(code) {
  76. if (code === null) {
  77. return after(code)
  78. }
  79. if (markdownLineEnding(code)) {
  80. return effects.attempt(furtherStart, atBreak, after)(code)
  81. }
  82. effects.enter('codeFlowValue')
  83. return inside(code)
  84. }
  85. /**
  86. * In code content.
  87. *
  88. * ```markdown
  89. * > | aaa
  90. * ^^^^
  91. * ```
  92. *
  93. * @type {State}
  94. */
  95. function inside(code) {
  96. if (code === null || markdownLineEnding(code)) {
  97. effects.exit('codeFlowValue')
  98. return atBreak(code)
  99. }
  100. effects.consume(code)
  101. return inside
  102. }
  103. /** @type {State} */
  104. function after(code) {
  105. effects.exit('codeIndented')
  106. // To do: allow interrupting like `markdown-rs`.
  107. // Feel free to interrupt.
  108. // tokenizer.interrupt = false
  109. return ok(code)
  110. }
  111. }
  112. /**
  113. * @this {TokenizeContext}
  114. * @type {Tokenizer}
  115. */
  116. function tokenizeFurtherStart(effects, ok, nok) {
  117. const self = this
  118. return furtherStart
  119. /**
  120. * At eol, trying to parse another indent.
  121. *
  122. * ```markdown
  123. * > | aaa
  124. * ^
  125. * | bbb
  126. * ```
  127. *
  128. * @type {State}
  129. */
  130. function furtherStart(code) {
  131. // To do: improve `lazy` / `pierce` handling.
  132. // If this is a lazy line, it can’t be code.
  133. if (self.parser.lazy[self.now().line]) {
  134. return nok(code)
  135. }
  136. if (markdownLineEnding(code)) {
  137. effects.enter('lineEnding')
  138. effects.consume(code)
  139. effects.exit('lineEnding')
  140. return furtherStart
  141. }
  142. // To do: the code here in `micromark-js` is a bit different from
  143. // `markdown-rs` because there it can attempt spaces.
  144. // We can’t yet.
  145. //
  146. // To do: use an improved `space_or_tab` function like `markdown-rs`,
  147. // so that we can drop the next state.
  148. return factorySpace(effects, afterPrefix, 'linePrefix', 4 + 1)(code)
  149. }
  150. /**
  151. * At start, after 1 or 4 spaces.
  152. *
  153. * ```markdown
  154. * > | aaa
  155. * ^
  156. * ```
  157. *
  158. * @type {State}
  159. */
  160. function afterPrefix(code) {
  161. const tail = self.events[self.events.length - 1]
  162. return tail &&
  163. tail[1].type === 'linePrefix' &&
  164. tail[2].sliceSerialize(tail[1], true).length >= 4
  165. ? ok(code)
  166. : markdownLineEnding(code)
  167. ? furtherStart(code)
  168. : nok(code)
  169. }
  170. }