index.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Process definition lists
  2. //
  3. 'use strict';
  4. module.exports = function deflist_plugin(md) {
  5. var isSpace = md.utils.isSpace;
  6. // Search `[:~][\n ]`, returns next pos after marker on success
  7. // or -1 on fail.
  8. function skipMarker(state, line) {
  9. var pos, marker,
  10. start = state.bMarks[line] + state.tShift[line],
  11. max = state.eMarks[line];
  12. if (start >= max) { return -1; }
  13. // Check bullet
  14. marker = state.src.charCodeAt(start++);
  15. if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; }
  16. pos = state.skipSpaces(start);
  17. // require space after ":"
  18. if (start === pos) { return -1; }
  19. // no empty definitions, e.g. " : "
  20. if (pos >= max) { return -1; }
  21. return start;
  22. }
  23. function markTightParagraphs(state, idx) {
  24. var i, l,
  25. level = state.level + 2;
  26. for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
  27. if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
  28. state.tokens[i + 2].hidden = true;
  29. state.tokens[i].hidden = true;
  30. i += 2;
  31. }
  32. }
  33. }
  34. function deflist(state, startLine, endLine, silent) {
  35. var ch,
  36. contentStart,
  37. ddLine,
  38. dtLine,
  39. itemLines,
  40. listLines,
  41. listTokIdx,
  42. max,
  43. nextLine,
  44. offset,
  45. oldDDIndent,
  46. oldIndent,
  47. oldParentType,
  48. oldSCount,
  49. oldTShift,
  50. oldTight,
  51. pos,
  52. prevEmptyEnd,
  53. tight,
  54. token;
  55. if (silent) {
  56. // quirk: validation mode validates a dd block only, not a whole deflist
  57. if (state.ddIndent < 0) { return false; }
  58. return skipMarker(state, startLine) >= 0;
  59. }
  60. nextLine = startLine + 1;
  61. if (nextLine >= endLine) { return false; }
  62. if (state.isEmpty(nextLine)) {
  63. nextLine++;
  64. if (nextLine >= endLine) { return false; }
  65. }
  66. if (state.sCount[nextLine] < state.blkIndent) { return false; }
  67. contentStart = skipMarker(state, nextLine);
  68. if (contentStart < 0) { return false; }
  69. // Start list
  70. listTokIdx = state.tokens.length;
  71. tight = true;
  72. token = state.push('dl_open', 'dl', 1);
  73. token.map = listLines = [ startLine, 0 ];
  74. //
  75. // Iterate list items
  76. //
  77. dtLine = startLine;
  78. ddLine = nextLine;
  79. // One definition list can contain multiple DTs,
  80. // and one DT can be followed by multiple DDs.
  81. //
  82. // Thus, there is two loops here, and label is
  83. // needed to break out of the second one
  84. //
  85. /*eslint no-labels:0,block-scoped-var:0*/
  86. OUTER:
  87. for (;;) {
  88. prevEmptyEnd = false;
  89. token = state.push('dt_open', 'dt', 1);
  90. token.map = [ dtLine, dtLine ];
  91. token = state.push('inline', '', 0);
  92. token.map = [ dtLine, dtLine ];
  93. token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim();
  94. token.children = [];
  95. token = state.push('dt_close', 'dt', -1);
  96. for (;;) {
  97. token = state.push('dd_open', 'dd', 1);
  98. token.map = itemLines = [ nextLine, 0 ];
  99. pos = contentStart;
  100. max = state.eMarks[ddLine];
  101. offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine]);
  102. while (pos < max) {
  103. ch = state.src.charCodeAt(pos);
  104. if (isSpace(ch)) {
  105. if (ch === 0x09) {
  106. offset += 4 - offset % 4;
  107. } else {
  108. offset++;
  109. }
  110. } else {
  111. break;
  112. }
  113. pos++;
  114. }
  115. contentStart = pos;
  116. oldTight = state.tight;
  117. oldDDIndent = state.ddIndent;
  118. oldIndent = state.blkIndent;
  119. oldTShift = state.tShift[ddLine];
  120. oldSCount = state.sCount[ddLine];
  121. oldParentType = state.parentType;
  122. state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2;
  123. state.tShift[ddLine] = contentStart - state.bMarks[ddLine];
  124. state.sCount[ddLine] = offset;
  125. state.tight = true;
  126. state.parentType = 'deflist';
  127. state.md.block.tokenize(state, ddLine, endLine, true);
  128. // If any of list item is tight, mark list as tight
  129. if (!state.tight || prevEmptyEnd) {
  130. tight = false;
  131. }
  132. // Item become loose if finish with empty line,
  133. // but we should filter last element, because it means list finish
  134. prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1);
  135. state.tShift[ddLine] = oldTShift;
  136. state.sCount[ddLine] = oldSCount;
  137. state.tight = oldTight;
  138. state.parentType = oldParentType;
  139. state.blkIndent = oldIndent;
  140. state.ddIndent = oldDDIndent;
  141. token = state.push('dd_close', 'dd', -1);
  142. itemLines[1] = nextLine = state.line;
  143. if (nextLine >= endLine) { break OUTER; }
  144. if (state.sCount[nextLine] < state.blkIndent) { break OUTER; }
  145. contentStart = skipMarker(state, nextLine);
  146. if (contentStart < 0) { break; }
  147. ddLine = nextLine;
  148. // go to the next loop iteration:
  149. // insert DD tag and repeat checking
  150. }
  151. if (nextLine >= endLine) { break; }
  152. dtLine = nextLine;
  153. if (state.isEmpty(dtLine)) { break; }
  154. if (state.sCount[dtLine] < state.blkIndent) { break; }
  155. ddLine = dtLine + 1;
  156. if (ddLine >= endLine) { break; }
  157. if (state.isEmpty(ddLine)) { ddLine++; }
  158. if (ddLine >= endLine) { break; }
  159. if (state.sCount[ddLine] < state.blkIndent) { break; }
  160. contentStart = skipMarker(state, ddLine);
  161. if (contentStart < 0) { break; }
  162. // go to the next loop iteration:
  163. // insert DT and DD tags and repeat checking
  164. }
  165. // Finilize list
  166. token = state.push('dl_close', 'dl', -1);
  167. listLines[1] = nextLine;
  168. state.line = nextLine;
  169. // mark paragraphs tight if needed
  170. if (tight) {
  171. markTightParagraphs(state, listTokIdx);
  172. }
  173. return true;
  174. }
  175. md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference', 'blockquote' ] });
  176. };