math.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import { InlineContext, MarkdownConfig, InlineParser } from '@lezer/markdown'
  2. const DOLLAR = 36 /* '$' */
  3. // Whitespace (including the start/end of the inline section, where `char`
  4. // returns -1).
  5. const isSpace = (code: number) =>
  6. code === -1 || code === 32 || code === 9 || code === 10 || code === 13
  7. const isDigit = (code: number) => code >= 48 && code <= 57
  8. const InlineMathDelim = { resolve: 'InlineMath', mark: 'InlineMathMark' }
  9. const BlockMathDelim = { resolve: 'BlockMath', mark: 'BlockMathMark' }
  10. // Inline parsing runs over the whole paragraph, so `$$…$$` handles multi-line
  11. // equations as long as there is no blank line inside them.
  12. const blockMathParser: InlineParser = {
  13. name: 'BlockMath',
  14. parse(cx: InlineContext, next: number, pos: number) {
  15. if (next !== DOLLAR || cx.char(pos + 1) !== DOLLAR) {
  16. return -1
  17. }
  18. return cx.addDelimiter(BlockMathDelim, pos, pos + 2, true, true)
  19. },
  20. // Run before the default `Emphasis` parser, and (via `InlineMath`'s `after`)
  21. // before the single-dollar inline parser, so `$$` is matched as a display
  22. // delimiter rather than two empty inline ones.
  23. before: 'Emphasis',
  24. }
  25. const inlineMathParser: InlineParser = {
  26. name: 'InlineMath',
  27. parse(cx: InlineContext, next: number, pos: number) {
  28. if (next !== DOLLAR) {
  29. return -1
  30. }
  31. const before = cx.char(pos - 1)
  32. const after = cx.char(pos + 1)
  33. const canOpen = !isSpace(after)
  34. const canClose = !isSpace(before) && !isDigit(after)
  35. if (!canOpen && !canClose) {
  36. return -1
  37. }
  38. return cx.addDelimiter(InlineMathDelim, pos, pos + 1, canOpen, canClose)
  39. },
  40. // Run after `BlockMath` so `$$` is tried as a display delimiter first, but
  41. // still before `Emphasis`. The default `Escape` parser consumes `\$` first.
  42. after: 'BlockMath',
  43. }
  44. export const Math: MarkdownConfig = {
  45. // No `style` is set on these nodes: math is rendered by the visual editor
  46. // (which slices the raw source), so the grammar change stays inert for
  47. // source-mode syntax highlighting and non-experiment users.
  48. defineNodes: ['InlineMath', 'InlineMathMark', 'BlockMath', 'BlockMathMark'],
  49. parseInline: [blockMathParser, inlineMathParser],
  50. }