blockquote.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. wasOutdented,
  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. // skip spaces after ">" and re-calculate offset
  36. initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]);
  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. wasOutdented = false;
  90. // Search the end of the block
  91. //
  92. // Block ends with either:
  93. // 1. an empty line outside:
  94. // ```
  95. // > test
  96. //
  97. // ```
  98. // 2. an empty line inside:
  99. // ```
  100. // >
  101. // test
  102. // ```
  103. // 3. another tag:
  104. // ```
  105. // > test
  106. // - - -
  107. // ```
  108. for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
  109. // check if it's outdented, i.e. it's inside list item and indented
  110. // less than said list item:
  111. //
  112. // ```
  113. // 1. anything
  114. // > current blockquote
  115. // 2. checking this line
  116. // ```
  117. if (state.sCount[nextLine] < state.blkIndent) wasOutdented = true;
  118. pos = state.bMarks[nextLine] + state.tShift[nextLine];
  119. max = state.eMarks[nextLine];
  120. if (pos >= max) {
  121. // Case 1: line is not inside the blockquote, and this line is empty.
  122. break;
  123. }
  124. if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !wasOutdented) {
  125. // This line is inside the blockquote.
  126. // skip spaces after ">" and re-calculate offset
  127. initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]);
  128. // skip one optional space after '>'
  129. if (state.src.charCodeAt(pos) === 0x20 /* space */) {
  130. // ' > test '
  131. // ^ -- position start of line here:
  132. pos++;
  133. initial++;
  134. offset++;
  135. adjustTab = false;
  136. spaceAfterMarker = true;
  137. } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) {
  138. spaceAfterMarker = true;
  139. if ((state.bsCount[nextLine] + offset) % 4 === 3) {
  140. // ' >\t test '
  141. // ^ -- position start of line here (tab has width===1)
  142. pos++;
  143. initial++;
  144. offset++;
  145. adjustTab = false;
  146. } else {
  147. // ' >\t test '
  148. // ^ -- position start of line here + shift bsCount slightly
  149. // to make extra space appear
  150. adjustTab = true;
  151. }
  152. } else {
  153. spaceAfterMarker = false;
  154. }
  155. oldBMarks.push(state.bMarks[nextLine]);
  156. state.bMarks[nextLine] = pos;
  157. while (pos < max) {
  158. ch = state.src.charCodeAt(pos);
  159. if (isSpace(ch)) {
  160. if (ch === 0x09) {
  161. offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4;
  162. } else {
  163. offset++;
  164. }
  165. } else {
  166. break;
  167. }
  168. pos++;
  169. }
  170. lastLineEmpty = pos >= max;
  171. oldBSCount.push(state.bsCount[nextLine]);
  172. state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0);
  173. oldSCount.push(state.sCount[nextLine]);
  174. state.sCount[nextLine] = offset - initial;
  175. oldTShift.push(state.tShift[nextLine]);
  176. state.tShift[nextLine] = pos - state.bMarks[nextLine];
  177. continue;
  178. }
  179. // Case 2: line is not inside the blockquote, and the last line was empty.
  180. if (lastLineEmpty) { break; }
  181. // Case 3: another tag found.
  182. terminate = false;
  183. for (i = 0, l = terminatorRules.length; i < l; i++) {
  184. if (terminatorRules[i](state, nextLine, endLine, true)) {
  185. terminate = true;
  186. break;
  187. }
  188. }
  189. if (terminate) {
  190. // Quirk to enforce "hard termination mode" for paragraphs;
  191. // normally if you call `tokenize(state, startLine, nextLine)`,
  192. // paragraphs will look below nextLine for paragraph continuation,
  193. // but if blockquote is terminated by another tag, they shouldn't
  194. state.lineMax = nextLine;
  195. if (state.blkIndent !== 0) {
  196. // state.blkIndent was non-zero, we now set it to zero,
  197. // so we need to re-calculate all offsets to appear as
  198. // if indent wasn't changed
  199. oldBMarks.push(state.bMarks[nextLine]);
  200. oldBSCount.push(state.bsCount[nextLine]);
  201. oldTShift.push(state.tShift[nextLine]);
  202. oldSCount.push(state.sCount[nextLine]);
  203. state.sCount[nextLine] -= state.blkIndent;
  204. }
  205. break;
  206. }
  207. oldBMarks.push(state.bMarks[nextLine]);
  208. oldBSCount.push(state.bsCount[nextLine]);
  209. oldTShift.push(state.tShift[nextLine]);
  210. oldSCount.push(state.sCount[nextLine]);
  211. // A negative indentation means that this is a paragraph continuation
  212. //
  213. state.sCount[nextLine] = -1;
  214. }
  215. oldIndent = state.blkIndent;
  216. state.blkIndent = 0;
  217. token = state.push('blockquote_open', 'blockquote', 1);
  218. token.markup = '>';
  219. token.map = lines = [ startLine, 0 ];
  220. state.md.block.tokenize(state, startLine, nextLine);
  221. token = state.push('blockquote_close', 'blockquote', -1);
  222. token.markup = '>';
  223. state.lineMax = oldLineMax;
  224. state.parentType = oldParentType;
  225. lines[1] = state.line;
  226. // Restore original tShift; this might not be necessary since the parser
  227. // has already been here, but just to make sure we can do that.
  228. for (i = 0; i < oldTShift.length; i++) {
  229. state.bMarks[i + startLine] = oldBMarks[i];
  230. state.tShift[i + startLine] = oldTShift[i];
  231. state.sCount[i + startLine] = oldSCount[i];
  232. state.bsCount[i + startLine] = oldBSCount[i];
  233. }
  234. state.blkIndent = oldIndent;
  235. return true;
  236. };