3 /* eslint-disable max-params */
5 var trim = require('trim');
6 var repeat = require('repeat-string');
7 var decimal = require('is-decimal');
8 var getIndent = require('../util/get-indentation');
9 var removeIndent = require('../util/remove-indentation');
10 var interrupt = require('../util/interrupt');
12 module.exports = list;
15 var C_UNDERSCORE = '_';
22 var C_PAREN_CLOSE = ')';
26 var EXPRESSION_LOOSE_LIST_ITEM = /\n\n(?!\s*$)/;
27 var EXPRESSION_TASK_ITEM = /^\[([ \t]|x|X)][ \t]/;
28 var EXPRESSION_BULLET = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/;
29 var EXPRESSION_PEDANTIC_BULLET = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/;
30 var EXPRESSION_INITIAL_INDENT = /^( {1,4}|\t)?/gm;
32 /* Map of characters which can be used to mark
34 var LIST_UNORDERED_MARKERS = {};
36 LIST_UNORDERED_MARKERS[C_ASTERISK] = true;
37 LIST_UNORDERED_MARKERS[C_PLUS] = true;
38 LIST_UNORDERED_MARKERS[C_DASH] = true;
40 /* Map of characters which can be used to mark
41 * list-items after a digit. */
42 var LIST_ORDERED_MARKERS = {};
44 LIST_ORDERED_MARKERS[C_DOT] = true;
46 /* Map of characters which can be used to mark
47 * list-items after a digit. */
48 var LIST_ORDERED_COMMONMARK_MARKERS = {};
50 LIST_ORDERED_COMMONMARK_MARKERS[C_DOT] = true;
51 LIST_ORDERED_COMMONMARK_MARKERS[C_PAREN_CLOSE] = true;
53 function list(eat, value, silent) {
55 var commonmark = self.options.commonmark;
56 var pedantic = self.options.pedantic;
57 var tokenizers = self.blockTokenizers;
58 var interuptors = self.interruptList;
61 var length = value.length;
88 while (index < length) {
89 character = value.charAt(index);
91 if (character === C_TAB) {
92 size += TAB_SIZE - (size % TAB_SIZE);
93 } else if (character === C_SPACE) {
102 if (size >= TAB_SIZE) {
106 character = value.charAt(index);
108 markers = commonmark ?
109 LIST_ORDERED_COMMONMARK_MARKERS :
110 LIST_ORDERED_MARKERS;
112 if (LIST_UNORDERED_MARKERS[character] === true) {
119 while (index < length) {
120 character = value.charAt(index);
122 if (!decimal(character)) {
130 character = value.charAt(index);
132 if (!queue || markers[character] !== true) {
136 start = parseInt(queue, 10);
140 character = value.charAt(++index);
142 if (character !== C_SPACE && character !== C_TAB) {
155 while (index < length) {
156 nextIndex = value.indexOf(C_NEWLINE, index);
161 if (nextIndex === -1) {
165 end = index + TAB_SIZE;
168 while (index < length) {
169 character = value.charAt(index);
171 if (character === C_TAB) {
172 size += TAB_SIZE - (size % TAB_SIZE);
173 } else if (character === C_SPACE) {
182 if (size >= TAB_SIZE) {
186 if (item && size >= item.indent) {
190 character = value.charAt(index);
191 currentMarker = null;
194 if (LIST_UNORDERED_MARKERS[character] === true) {
195 currentMarker = character;
201 while (index < length) {
202 character = value.charAt(index);
204 if (!decimal(character)) {
212 character = value.charAt(index);
215 if (queue && markers[character] === true) {
216 currentMarker = character;
217 size += queue.length + 1;
222 character = value.charAt(index);
224 if (character === C_TAB) {
225 size += TAB_SIZE - (size % TAB_SIZE);
227 } else if (character === C_SPACE) {
228 end = index + TAB_SIZE;
230 while (index < end) {
231 if (value.charAt(index) !== C_SPACE) {
239 if (index === end && value.charAt(index) === C_SPACE) {
240 index -= TAB_SIZE - 1;
241 size -= TAB_SIZE - 1;
243 } else if (character !== C_NEWLINE && character !== '') {
244 currentMarker = null;
250 if (!pedantic && marker !== currentMarker) {
256 if (!commonmark && !indented && value.charAt(startIndex) === C_SPACE) {
258 } else if (commonmark && item) {
259 indented = size >= item.indent || size > TAB_SIZE;
266 line = value.slice(startIndex, nextIndex);
267 content = startIndex === index ? line : value.slice(index, nextIndex);
270 currentMarker === C_ASTERISK ||
271 currentMarker === C_UNDERSCORE ||
272 currentMarker === C_DASH
274 if (tokenizers.thematicBreak.call(self, eat, line, true)) {
280 empty = !trim(content).length;
282 if (indented && item) {
283 item.value = item.value.concat(emptyLines, line);
284 allLines = allLines.concat(emptyLines, line);
286 } else if (prefixed) {
287 if (emptyLines.length !== 0) {
289 item.trail = emptyLines.concat();
299 allLines = allLines.concat(emptyLines, line);
306 emptyLines.push(line);
312 if (interrupt(interuptors, tokenizers, self, [eat, line, true])) {
316 item.value = item.value.concat(emptyLines, line);
317 allLines = allLines.concat(emptyLines, line);
321 index = nextIndex + 1;
324 node = eat(allLines.join(C_NEWLINE)).reset({
332 enterTop = self.enterList();
333 exitBlockquote = self.enterBlock();
336 length = items.length;
338 while (++index < length) {
339 item = items[index].value.join(C_NEWLINE);
342 item = eat(item)(listItem(self, item, now), node);
348 item = items[index].trail.join(C_NEWLINE);
350 if (index !== length - 1) {
360 node.loose = isLoose;
365 function listItem(ctx, value, position) {
366 var offsets = ctx.offset;
367 var fn = ctx.options.pedantic ? pedanticListItem : normalListItem;
372 value = fn.apply(null, arguments);
374 if (ctx.options.gfm) {
375 task = value.match(EXPRESSION_TASK_ITEM);
378 indent = task[0].length;
379 checked = task[1].toLowerCase() === C_X_LOWER;
380 offsets[position.line] += indent;
381 value = value.slice(indent);
387 loose: EXPRESSION_LOOSE_LIST_ITEM.test(value) ||
388 value.charAt(value.length - 1) === C_NEWLINE,
390 children: ctx.tokenizeBlock(value, position)
394 /* Create a list-item using overly simple mechanics. */
395 function pedanticListItem(ctx, value, position) {
396 var offsets = ctx.offset;
397 var line = position.line;
399 /* Remove the list-item’s bullet. */
400 value = value.replace(EXPRESSION_PEDANTIC_BULLET, replacer);
402 /* The initial line was also matched by the below, so
403 * we reset the `line`. */
404 line = position.line;
406 return value.replace(EXPRESSION_INITIAL_INDENT, replacer);
408 /* A simple replacer which removed all matches,
409 * and adds their length to `offset`. */
410 function replacer($0) {
411 offsets[line] = (offsets[line] || 0) + $0.length;
418 /* Create a list-item using sane mechanics. */
419 function normalListItem(ctx, value, position) {
420 var offsets = ctx.offset;
421 var line = position.line;
430 /* Remove the list-item’s bullet. */
431 value = value.replace(EXPRESSION_BULLET, replacer);
433 lines = value.split(C_NEWLINE);
435 trimmedLines = removeIndent(value, getIndent(max).indent).split(C_NEWLINE);
437 /* We replaced the initial bullet with something
438 * else above, which was used to trick
439 * `removeIndentation` into removing some more
440 * characters when possible. However, that could
441 * result in the initial line to be stripped more
442 * than it should be. */
443 trimmedLines[0] = rest;
445 offsets[line] = (offsets[line] || 0) + bullet.length;
449 length = lines.length;
451 while (++index < length) {
452 offsets[line] = (offsets[line] || 0) +
453 lines[index].length - trimmedLines[index].length;
457 return trimmedLines.join(C_NEWLINE);
459 function replacer($0, $1, $2, $3, $4) {
460 bullet = $1 + $2 + $3;
463 /* Make sure that the first nine numbered list items
464 * can indent with an extra space. That is, when
465 * the bullet did not receive an extra final space. */
466 if (Number($2) < 10 && bullet.length % 2 === 1) {
470 max = $1 + repeat(C_SPACE, $2.length) + $3;