blockquote.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Block quotes
  2. 'use strict';
  3. var isSpace = require('../common/utils').isSpace;
  4. module.exports = function blockquote(state, startLine, endLine, silent) {
  5. var adjustTab,
  6. ch,
  7. i,
  8. initial,
  9. l,
  10. lastLineEmpty,
  11. lines,
  12. nextLine,
  13. offset,
  14. oldBMarks,
  15. oldBSCount,
  16. oldIndent,
  17. oldParentType,
  18. oldSCount,
  19. oldTShift,
  20. spaceAfterMarker,
  21. terminate,
  22. terminatorRules,
  23. token,
  24. isOutdented,
  25. oldLineMax = state.lineMax,
  26. pos = state.bMarks[startLine] + state.tShift[startLine],
  27. max = state.eMarks[startLine];
  28. // if it's indented more than 3 spaces, it should be a code block
  29. if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
  30. // check the block quote marker
  31. if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; }
  32. // we know that it's going to be a valid blockquote,
  33. // so no point trying to find the end of it in silent mode
  34. if (silent) { return true; }
  35. // set offset past spaces and ">"
  36. initial = offset = state.sCount[startLine] + 1;
  37. // skip one optional space after '>'
  38. if (state.src.charCodeAt(pos) === 0x20 /* space */) {
  39. // ' > test '
  40. // ^ -- position start of line here:
  41. pos++;
  42. initial++;
  43. offset++;
  44. adjustTab = false;
  45. spaceAfterMarker = true;
  46. } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) {
  47. spaceAfterMarker = true;
  48. if ((state.bsCount[startLine] + offset) % 4 === 3) {
  49. // ' >\t test '
  50. // ^ -- position start of line here (tab has width===1)
  51. pos++;
  52. initial++;
  53. offset++;
  54. adjustTab = false;
  55. } else {
  56. // ' >\t test '
  57. // ^ -- position start of line here + shift bsCount slightly
  58. // to make extra space appear
  59. adjustTab = true;
  60. }
  61. } else {
  62. spaceAfterMarker = false;
  63. }
  64. oldBMarks = [ state.bMarks[startLine] ];
  65. state.bMarks[startLine] = pos;
  66. while (pos < max) {
  67. ch = state.src.charCodeAt(pos);
  68. if (isSpace(ch)) {
  69. if (ch === 0x09) {
  70. offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4;
  71. } else {
  72. offset++;
  73. }
  74. } else {
  75. break;
  76. }
  77. pos++;
  78. }
  79. oldBSCount = [ state.bsCount[startLine] ];
  80. state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0);
  81. lastLineEmpty = pos >= max;
  82. oldSCount = [ state.sCount[startLine] ];
  83. state.sCount[startLine] = offset - initial;
  84. oldTShift = [ state.tShift[startLine] ];
  85. state.tShift[startLine] = pos - state.bMarks[startLine];
  86. terminatorRules = state.md.block.ruler.getRules('blockquote');
  87. oldParentType = state.parentType;
  88. state.parentType = 'blockquote';
  89. // Search the end of the block
  90. //
  91. // Block ends with either:
  92. // 1. an empty line outside:
  93. // ```
  94. // > test
  95. //
  96. // ```
  97. // 2. an empty line inside:
  98. // ```
  99. // >
  100. // test
  101. // ```
  102. // 3. another tag:
  103. // ```
  104. // > test
  105. // - - -
  106. // ```
  107. for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
  108. // check if it's outdented, i.e. it's inside list item and indented
  109. // less than said list item:
  110. //
  111. // ```
  112. // 1. anything
  113. // > current blockquote
  114. // 2. checking this line
  115. // ```
  116. isOutdented = state.sCount[nextLine] < state.blkIndent;
  117. pos = state.bMarks[nextLine] + state.tShift[nextLine];
  118. max = state.eMarks[nextLine];
  119. if (pos >= max) {
  120. // Case 1: line is not inside the blockquote, and this line is empty.
  121. break;
  122. }
  123. if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !isOutdented) {
  124. // This line is inside the blockquote.
  125. // set offset past spaces and ">"
  126. initial = offset = state.sCount[nextLine] + 1;
  127. // skip one optional space after '>'
  128. if (state.src.charCodeAt(pos) === 0x20 /* space */) {
  129. // ' > test '
  130. // ^ -- position start of line here:
  131. pos++;
  132. initial++;
  133. offset++;
  134. adjustTab = false;
  135. spaceAfterMarker = true;
  136. } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) {
  137. spaceAfterMarker = true;
  138. if ((state.bsCount[nextLine] + offset) % 4 === 3) {
  139. // ' >\t test '
  140. // ^ -- position start of line here (tab has width===1)
  141. pos++;
  142. initial++;
  143. offset++;
  144. adjustTab = false;
  145. } else {
  146. // ' >\t test '
  147. // ^ -- position start of line here + shift bsCount slightly
  148. // to make extra space appear
  149. adjustTab = true;
  150. }
  151. } else {
  152. spaceAfterMarker = false;
  153. }
  154. oldBMarks.push(state.bMarks[nextLine]);
  155. state.bMarks[nextLine] = pos;
  156. while (pos < max) {
  157. ch = state.src.charCodeAt(pos);
  158. if (isSpace(ch)) {
  159. if (ch === 0x09) {
  160. offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4;
  161. } else {
  162. offset++;
  163. }
  164. } else {
  165. break;
  166. }
  167. pos++;
  168. }
  169. lastLineEmpty = pos >= max;
  170. oldBSCount.push(state.bsCount[nextLine]);
  171. state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0);
  172. oldSCount.push(state.sCount[nextLine]);
  173. state.sCount[nextLine] = offset - initial;
  174. oldTShift.push(state.tShift[nextLine]);
  175. state.tShift[nextLine] = pos - state.bMarks[nextLine];
  176. continue;
  177. }
  178. // Case 2: line is not inside the blockquote, and the last line was empty.
  179. if (lastLineEmpty) { break; }
  180. // Case 3: another tag found.
  181. terminate = false;
  182. for (i = 0, l = terminatorRules.length; i < l; i++) {
  183. if (terminatorRules[i](state, nextLine, endLine, true)) {
  184. terminate = true;
  185. break;
  186. }
  187. }
  188. if (terminate) {
  189. // Quirk to enforce "hard termination mode" for paragraphs;
  190. // normally if you call `tokenize(state, startLine, nextLine)`,
  191. // paragraphs will look below nextLine for paragraph continuation,
  192. // but if blockquote is terminated by another tag, they shouldn't
  193. state.lineMax = nextLine;
  194. if (state.blkIndent !== 0) {
  195. // state.blkIndent was non-zero, we now set it to zero,
  196. // so we need to re-calculate all offsets to appear as
  197. // if indent wasn't changed
  198. oldBMarks.push(state.bMarks[nextLine]);
  199. oldBSCount.push(state.bsCount[nextLine]);
  200. oldTShift.push(state.tShift[nextLine]);
  201. oldSCount.push(state.sCount[nextLine]);
  202. state.sCount[nextLine] -= state.blkIndent;
  203. }
  204. break;
  205. }
  206. oldBMarks.push(state.bMarks[nextLine]);
  207. oldBSCount.push(state.bsCount[nextLine]);
  208. oldTShift.push(state.tShift[nextLine]);
  209. oldSCount.push(state.sCount[nextLine]);
  210. // A negative indentation means that this is a paragraph continuation
  211. //
  212. state.sCount[nextLine] = -1;
  213. }
  214. oldIndent = state.blkIndent;
  215. state.blkIndent = 0;
  216. token = state.push('blockquote_open', 'blockquote', 1);
  217. token.markup = '>';
  218. token.map = lines = [ startLine, 0 ];
  219. state.md.block.tokenize(state, startLine, nextLine);
  220. token = state.push('blockquote_close', 'blockquote', -1);
  221. token.markup = '>';
  222. state.lineMax = oldLineMax;
  223. state.parentType = oldParentType;
  224. lines[1] = state.line;
  225. // Restore original tShift; this might not be necessary since the parser
  226. // has already been here, but just to make sure we can do that.
  227. for (i = 0; i < oldTShift.length; i++) {
  228. state.bMarks[i + startLine] = oldBMarks[i];
  229. state.tShift[i + startLine] = oldTShift[i];
  230. state.sCount[i + startLine] = oldSCount[i];
  231. state.bsCount[i + startLine] = oldBSCount[i];
  232. }
  233. state.blkIndent = oldIndent;
  234. return true;
  235. };