code-indented.js 4.4 KB

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