You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
4.2 KiB

  1. // Test if potential opening or closing delimieter
  2. // Assumes that there is a "$" at state.src[pos]
  3. function isValidDelim (state, pos) {
  4. let prevChar
  5. let nextChar
  6. let max = state.posMax
  7. let canOpen = true
  8. let canClose = true
  9. prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
  10. nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
  11. // Check non-whitespace conditions for opening and closing, and
  12. // check that closing delimeter isn't followed by a number
  13. if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ ||
  14. (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) {
  15. canClose = false
  16. }
  17. if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) {
  18. canOpen = false
  19. }
  20. return {
  21. canOpen: canOpen,
  22. canClose: canClose
  23. }
  24. }
  25. export default {
  26. katexInline (state, silent) {
  27. let start, match, token, res, pos
  28. if (state.src[state.pos] !== '$') { return false }
  29. res = isValidDelim(state, state.pos)
  30. if (!res.canOpen) {
  31. if (!silent) { state.pending += '$' }
  32. state.pos += 1
  33. return true
  34. }
  35. // First check for and bypass all properly escaped delimieters
  36. // This loop will assume that the first leading backtick can not
  37. // be the first character in state.src, which is known since
  38. // we have found an opening delimieter already.
  39. start = state.pos + 1
  40. match = start
  41. while ((match = state.src.indexOf('$', match)) !== -1) {
  42. // Found potential $, look for escapes, pos will point to
  43. // first non escape when complete
  44. pos = match - 1
  45. while (state.src[pos] === '\\') { pos -= 1 }
  46. // Even number of escapes, potential closing delimiter found
  47. if (((match - pos) % 2) === 1) { break }
  48. match += 1
  49. }
  50. // No closing delimter found. Consume $ and continue.
  51. if (match === -1) {
  52. if (!silent) { state.pending += '$' }
  53. state.pos = start
  54. return true
  55. }
  56. // Check if we have empty content, ie: $$. Do not parse.
  57. if (match - start === 0) {
  58. if (!silent) { state.pending += '$$' }
  59. state.pos = start + 1
  60. return true
  61. }
  62. // Check for valid closing delimiter
  63. res = isValidDelim(state, match)
  64. if (!res.canClose) {
  65. if (!silent) { state.pending += '$' }
  66. state.pos = start
  67. return true
  68. }
  69. if (!silent) {
  70. token = state.push('katex_inline', 'math', 0)
  71. token.markup = '$'
  72. token.content = state.src
  73. // Extract the math part without the $
  74. .slice(start, match)
  75. // Escape the curly braces since they will be interpreted as
  76. // attributes by markdown-it-attrs (the "curly_attributes"
  77. // core rule)
  78. .replaceAll("{", "{{")
  79. .replaceAll("}", "}}")
  80. }
  81. state.pos = match + 1
  82. return true
  83. },
  84. katexBlock (state, start, end, silent) {
  85. let firstLine; let lastLine; let next; let lastPos; let found = false; let token
  86. let pos = state.bMarks[start] + state.tShift[start]
  87. let max = state.eMarks[start]
  88. if (pos + 2 > max) { return false }
  89. if (state.src.slice(pos, pos + 2) !== '$$') { return false }
  90. pos += 2
  91. firstLine = state.src.slice(pos, max)
  92. if (silent) { return true }
  93. if (firstLine.trim().slice(-2) === '$$') {
  94. // Single line expression
  95. firstLine = firstLine.trim().slice(0, -2)
  96. found = true
  97. }
  98. for (next = start; !found;) {
  99. next++
  100. if (next >= end) { break }
  101. pos = state.bMarks[next] + state.tShift[next]
  102. max = state.eMarks[next]
  103. if (pos < max && state.tShift[next] < state.blkIndent) {
  104. // non-empty line with negative indent should stop the list:
  105. break
  106. }
  107. if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
  108. lastPos = state.src.slice(0, max).lastIndexOf('$$')
  109. lastLine = state.src.slice(pos, lastPos)
  110. found = true
  111. }
  112. }
  113. state.line = next + 1
  114. token = state.push('katex_block', 'math', 0)
  115. token.block = true
  116. token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
  117. state.getLines(start + 1, next, state.tShift[start], true) +
  118. (lastLine && lastLine.trim() ? lastLine : '')
  119. token.map = [ start, state.line ]
  120. token.markup = '$$'
  121. return true
  122. }
  123. }