motion-markdown-it 4.4.0 → 8.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -16
  3. data/lib/motion-markdown-it.rb +7 -5
  4. data/lib/motion-markdown-it/common/html_blocks.rb +6 -2
  5. data/lib/motion-markdown-it/common/utils.rb +19 -4
  6. data/lib/motion-markdown-it/helpers/helper_wrapper.rb +9 -0
  7. data/lib/motion-markdown-it/helpers/parse_link_destination.rb +8 -7
  8. data/lib/motion-markdown-it/index.rb +60 -18
  9. data/lib/motion-markdown-it/parser_block.rb +7 -10
  10. data/lib/motion-markdown-it/parser_inline.rb +50 -14
  11. data/lib/motion-markdown-it/presets/commonmark.rb +7 -1
  12. data/lib/motion-markdown-it/presets/default.rb +4 -3
  13. data/lib/motion-markdown-it/presets/zero.rb +6 -1
  14. data/lib/motion-markdown-it/renderer.rb +46 -14
  15. data/lib/motion-markdown-it/rules_block/blockquote.rb +167 -31
  16. data/lib/motion-markdown-it/rules_block/code.rb +4 -3
  17. data/lib/motion-markdown-it/rules_block/fence.rb +9 -4
  18. data/lib/motion-markdown-it/rules_block/heading.rb +8 -3
  19. data/lib/motion-markdown-it/rules_block/hr.rb +10 -5
  20. data/lib/motion-markdown-it/rules_block/html_block.rb +6 -3
  21. data/lib/motion-markdown-it/rules_block/lheading.rb +64 -26
  22. data/lib/motion-markdown-it/rules_block/list.rb +91 -22
  23. data/lib/motion-markdown-it/rules_block/paragraph.rb +14 -9
  24. data/lib/motion-markdown-it/rules_block/reference.rb +24 -14
  25. data/lib/motion-markdown-it/rules_block/state_block.rb +79 -24
  26. data/lib/motion-markdown-it/rules_block/table.rb +52 -26
  27. data/lib/motion-markdown-it/rules_core/normalize.rb +1 -23
  28. data/lib/motion-markdown-it/rules_core/replacements.rb +22 -2
  29. data/lib/motion-markdown-it/rules_core/smartquotes.rb +41 -12
  30. data/lib/motion-markdown-it/rules_inline/autolink.rb +5 -4
  31. data/lib/motion-markdown-it/rules_inline/balance_pairs.rb +48 -0
  32. data/lib/motion-markdown-it/rules_inline/emphasis.rb +104 -149
  33. data/lib/motion-markdown-it/rules_inline/entity.rb +2 -2
  34. data/lib/motion-markdown-it/rules_inline/escape.rb +5 -3
  35. data/lib/motion-markdown-it/rules_inline/image.rb +12 -23
  36. data/lib/motion-markdown-it/rules_inline/link.rb +20 -25
  37. data/lib/motion-markdown-it/rules_inline/newline.rb +2 -1
  38. data/lib/motion-markdown-it/rules_inline/state_inline.rb +60 -1
  39. data/lib/motion-markdown-it/rules_inline/strikethrough.rb +81 -97
  40. data/lib/motion-markdown-it/rules_inline/text_collapse.rb +40 -0
  41. data/lib/motion-markdown-it/token.rb +46 -1
  42. data/lib/motion-markdown-it/version.rb +1 -1
  43. data/spec/motion-markdown-it/markdown_it_spec.rb +2 -2
  44. data/spec/motion-markdown-it/misc_spec.rb +90 -14
  45. data/spec/motion-markdown-it/testgen_helper.rb +1 -1
  46. data/spec/spec_helper.rb +2 -3
  47. metadata +13 -13
  48. data/lib/motion-markdown-it/common/url_schemas.rb +0 -173
  49. data/spec/motion-markdown-it/bench_mark_spec.rb +0 -44
@@ -6,7 +6,7 @@ module MarkdownIt
6
6
 
7
7
  #------------------------------------------------------------------------------
8
8
  def self.code(state, startLine, endLine, silent = true)
9
- return false if (state.tShift[startLine] - state.blkIndent < 4)
9
+ return false if (state.sCount[startLine] - state.blkIndent < 4)
10
10
 
11
11
  last = nextLine = startLine + 1
12
12
  while nextLine < endLine
@@ -14,7 +14,8 @@ module MarkdownIt
14
14
  nextLine += 1
15
15
  next
16
16
  end
17
- if (state.tShift[nextLine] - state.blkIndent >= 4)
17
+
18
+ if (state.sCount[nextLine] - state.blkIndent >= 4)
18
19
  nextLine += 1
19
20
  last = nextLine
20
21
  next
@@ -22,7 +23,7 @@ module MarkdownIt
22
23
  break
23
24
  end
24
25
 
25
- state.line = nextLine
26
+ state.line = last
26
27
 
27
28
  token = state.push('code_block', 'code', 0)
28
29
  token.content = state.getLines(startLine, last, 4 + state.blkIndent, true)
@@ -3,6 +3,7 @@
3
3
  module MarkdownIt
4
4
  module RulesBlock
5
5
  class Fence
6
+ extend Common::Utils
6
7
 
7
8
  #------------------------------------------------------------------------------
8
9
  def self.fence(state, startLine, endLine, silent)
@@ -10,6 +11,9 @@ module MarkdownIt
10
11
  pos = state.bMarks[startLine] + state.tShift[startLine]
11
12
  max = state.eMarks[startLine]
12
13
 
14
+ # if it's indented more than 3 spaces, it should be a code block
15
+ return false if state.sCount[startLine] - state.blkIndent >= 4
16
+
13
17
  return false if pos + 3 > max
14
18
 
15
19
  marker = state.src.charCodeAt(pos)
@@ -28,7 +32,7 @@ module MarkdownIt
28
32
  markup = state.src.slice(mem...pos)
29
33
  params = state.src.slice(pos...max)
30
34
 
31
- return false if params.include?('`')
35
+ return false if params.include?(fromCharCode(marker))
32
36
 
33
37
  # Since start is found, we can report success here in validation mode
34
38
  return true if silent
@@ -47,7 +51,7 @@ module MarkdownIt
47
51
  pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
48
52
  max = state.eMarks[nextLine];
49
53
 
50
- if pos < max && state.tShift[nextLine] < state.blkIndent
54
+ if pos < max && state.sCount[nextLine] < state.blkIndent
51
55
  # non-empty line with negative indent should stop the list:
52
56
  # - ```
53
57
  # test
@@ -56,7 +60,7 @@ module MarkdownIt
56
60
 
57
61
  next if state.src.charCodeAt(pos) != marker
58
62
 
59
- if state.tShift[nextLine] - state.blkIndent >= 4
63
+ if state.sCount[nextLine] - state.blkIndent >= 4
60
64
  # closing fence should be indented less than 4 spaces
61
65
  next
62
66
  end
@@ -77,7 +81,8 @@ module MarkdownIt
77
81
  end
78
82
 
79
83
  # If a fence has heading spaces, they should be removed from its inner block
80
- len = state.tShift[startLine]
84
+ len = state.sCount[startLine]
85
+
81
86
  state.line = nextLine + (haveEndMarker ? 1 : 0)
82
87
 
83
88
  token = state.push('fence', 'code', 0)
@@ -3,11 +3,16 @@
3
3
  module MarkdownIt
4
4
  module RulesBlock
5
5
  class Heading
6
+ extend Common::Utils
6
7
 
7
8
  #------------------------------------------------------------------------------
8
9
  def self.heading(state, startLine, endLine, silent)
9
10
  pos = state.bMarks[startLine] + state.tShift[startLine]
10
11
  max = state.eMarks[startLine]
12
+
13
+ # if it's indented more than 3 spaces, it should be a code block
14
+ return false if state.sCount[startLine] - state.blkIndent >= 4
15
+
11
16
  ch = state.src.charCodeAt(pos)
12
17
 
13
18
  return false if (ch != 0x23 || pos >= max)
@@ -22,15 +27,15 @@ module MarkdownIt
22
27
  ch = state.src.charCodeAt(pos)
23
28
  end
24
29
 
25
- return false if (level > 6 || (pos < max && ch != 0x20)) # space
30
+ return false if (level > 6 || (pos < max && !isSpace(ch)))
26
31
 
27
32
  return true if (silent)
28
33
 
29
34
  # Let's cut tails like ' ### ' from the end of string
30
35
 
31
- max = state.skipCharsBack(max, 0x20, pos) # space
36
+ max = state.skipSpacesBack(max, pos)
32
37
  tmp = state.skipCharsBack(max, 0x23, pos) # '#'
33
- if (tmp > pos && state.src.charCodeAt(tmp - 1) == 0x20) # space
38
+ if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1)))
34
39
  max = tmp
35
40
  end
36
41
 
@@ -3,11 +3,16 @@
3
3
  module MarkdownIt
4
4
  module RulesBlock
5
5
  class Hr
6
+ extend Common::Utils
6
7
 
7
8
  #------------------------------------------------------------------------------
8
9
  def self.hr(state, startLine, endLine, silent)
9
10
  pos = state.bMarks[startLine] + state.tShift[startLine]
10
11
  max = state.eMarks[startLine]
12
+
13
+ # if it's indented more than 3 spaces, it should be a code block
14
+ return false if (state.sCount[startLine] - state.blkIndent >= 4)
15
+
11
16
  marker = state.src.charCodeAt(pos)
12
17
  pos += 1
13
18
 
@@ -18,18 +23,18 @@ module MarkdownIt
18
23
  return false
19
24
  end
20
25
 
21
- # markers can be mixed with spaces, but there should be at least 3 one
26
+ # markers can be mixed with spaces, but there should be at least 3 of them
22
27
 
23
28
  cnt = 1
24
29
  while (pos < max)
25
30
  ch = state.src.charCodeAt(pos)
26
31
  pos += 1
27
- return false if (ch != marker && ch != 0x20) # space
28
- cnt += 1 if (ch == marker)
32
+ return false if ch != marker && !isSpace(ch)
33
+ cnt += 1 if ch == marker
29
34
  end
30
35
 
31
- return false if (cnt < 3)
32
- return true if (silent)
36
+ return false if cnt < 3
37
+ return true if silent
33
38
 
34
39
  state.line = startLine + 1
35
40
 
@@ -24,8 +24,11 @@ module MarkdownIt
24
24
  pos = state.bMarks[startLine] + state.tShift[startLine]
25
25
  max = state.eMarks[startLine]
26
26
 
27
+ # if it's indented more than 3 spaces, it should be a code block
28
+ return false if state.sCount[startLine] - state.blkIndent >= 4
29
+
27
30
  return false if !state.md.options[:html]
28
- return false if state.src.charCodeAt(pos) != 0x3C # <
31
+ return false if state.src.charCodeAt(pos) != 0x3C # <
29
32
 
30
33
  lineText = state.src.slice(pos...max)
31
34
 
@@ -34,7 +37,7 @@ module MarkdownIt
34
37
  break if HTML_SEQUENCES[i][0].match(lineText)
35
38
  i += 1
36
39
  end
37
-
40
+
38
41
  return false if i == HTML_SEQUENCES.length
39
42
 
40
43
  if silent
@@ -48,7 +51,7 @@ module MarkdownIt
48
51
  # Let's roll down till block end.
49
52
  if !HTML_SEQUENCES[i][1].match(lineText)
50
53
  while nextLine < endLine
51
- break if state.tShift[nextLine] < state.blkIndent
54
+ break if state.sCount[nextLine] < state.blkIndent
52
55
 
53
56
  pos = state.bMarks[nextLine] + state.tShift[nextLine]
54
57
  max = state.eMarks[nextLine]
@@ -6,46 +6,84 @@ module MarkdownIt
6
6
 
7
7
  #------------------------------------------------------------------------------
8
8
  def self.lheading(state, startLine, endLine, silent = true)
9
- nextLine = startLine + 1
10
-
11
- return false if (nextLine >= endLine)
12
- return false if (state.tShift[nextLine] < state.blkIndent)
13
-
14
- # Scan next line
15
-
16
- return false if (state.tShift[nextLine] - state.blkIndent > 3)
17
-
18
- pos = state.bMarks[nextLine] + state.tShift[nextLine]
19
- max = state.eMarks[nextLine]
20
-
21
- return false if (pos >= max)
22
-
23
- marker = state.src.charCodeAt(pos)
24
-
25
- return false if (marker != 0x2D && marker != 0x3D) # != '-' && != '='
26
-
27
- pos = state.skipChars(pos, marker)
28
- pos = state.skipSpaces(pos)
29
-
30
- return false if (pos < max)
31
-
32
- pos = state.bMarks[startLine] + state.tShift[startLine]
9
+ nextLine = startLine + 1
10
+ terminatorRules = state.md.block.ruler.getRules('paragraph')
11
+
12
+ # if it's indented more than 3 spaces, it should be a code block
13
+ return false if state.sCount[startLine] - state.blkIndent >= 4
14
+
15
+ oldParentType = state.parentType
16
+ state.parentType = 'paragraph' # use paragraph to match terminatorRules
17
+
18
+ # jump line-by-line until empty one or EOF
19
+ while nextLine < endLine && !state.isEmpty(nextLine)
20
+ # this would be a code block normally, but after paragraph
21
+ # it's considered a lazy continuation regardless of what's there
22
+ (nextLine += 1) and next if (state.sCount[nextLine] - state.blkIndent > 3)
23
+
24
+ #
25
+ # Check for underline in setext header
26
+ #
27
+ if state.sCount[nextLine] >= state.blkIndent
28
+ pos = state.bMarks[nextLine] + state.tShift[nextLine]
29
+ max = state.eMarks[nextLine]
30
+
31
+ if pos < max
32
+ marker = state.src.charCodeAt(pos)
33
+
34
+ if marker == 0x2D || marker == 0x3D # - or =
35
+ pos = state.skipChars(pos, marker)
36
+ pos = state.skipSpaces(pos)
37
+
38
+ if pos >= max
39
+ level = (marker == 0x3D ? 1 : 2) # =
40
+ break
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # quirk for blockquotes, this line should already be checked by that rule
47
+ (nextLine += 1) and next if state.sCount[nextLine] < 0
48
+
49
+ # Some tags can terminate paragraph without empty line.
50
+ terminate = false
51
+ l = terminatorRules.length
52
+ 0.upto(l - 1) do |i|
53
+ if terminatorRules[i].call(state, nextLine, endLine, true)
54
+ terminate = true
55
+ break
56
+ end
57
+ end
58
+ break if terminate
59
+
60
+ nextLine += 1
61
+ end
62
+
63
+ if !level
64
+ # Didn't find valid underline
65
+ return false
66
+ end
67
+
68
+ content = state.getLines(startLine, nextLine, state.blkIndent, false).strip
33
69
 
34
70
  state.line = nextLine + 1
35
- level = (marker == 0x3D ? 1 : 2) # =
36
71
 
37
72
  token = state.push('heading_open', "h#{level.to_s}", 1)
38
73
  token.markup = marker.chr
39
74
  token.map = [ startLine, state.line ]
40
75
 
41
76
  token = state.push('inline', '', 0)
42
- token.content = state.src.slice(pos...state.eMarks[startLine]).strip
77
+ # token.content = state.src.slice(pos...state.eMarks[startLine]).strip
78
+ token.content = content
43
79
  token.map = [ startLine, state.line - 1 ]
44
80
  token.children = []
45
81
 
46
82
  token = state.push('heading_close', "h#{level.to_s}", -1)
47
83
  token.markup = marker.chr
48
84
 
85
+ state.parentType = oldParentType
86
+
49
87
  return true
50
88
  end
51
89
 
@@ -3,8 +3,9 @@
3
3
  module MarkdownIt
4
4
  module RulesBlock
5
5
  class List
6
+ extend Common::Utils
6
7
 
7
- # Search `[-+*][\n ]`, returns next pos arter marker on success
8
+ # Search `[-+*][\n ]`, returns next pos after marker on success
8
9
  # or -1 on fail.
9
10
  #------------------------------------------------------------------------------
10
11
  def self.skipBulletListMarker(state, startLine)
@@ -20,9 +21,13 @@ module MarkdownIt
20
21
  return -1
21
22
  end
22
23
 
23
- if (pos < max && state.src.charCodeAt(pos) != 0x20)
24
- # " 1.test " - is not a list item
25
- return -1
24
+ if pos < max
25
+ ch = state.src.charCodeAt(pos)
26
+
27
+ if !isSpace(ch)
28
+ # " -test " - is not a list item
29
+ return -1
30
+ end
26
31
  end
27
32
 
28
33
  return pos
@@ -69,10 +74,13 @@ module MarkdownIt
69
74
  return -1
70
75
  end
71
76
 
77
+ if pos < max
78
+ ch = state.src.charCodeAt(pos)
72
79
 
73
- if (pos < max && state.src.charCodeAt(pos) != 0x20) # space
74
- # " 1.test " - is not a list item
75
- return -1
80
+ if !isSpace(ch)
81
+ # " 1.test " - is not a list item
82
+ return -1
83
+ end
76
84
  end
77
85
  return pos
78
86
  end
@@ -80,7 +88,7 @@ module MarkdownIt
80
88
  #------------------------------------------------------------------------------
81
89
  def self.markTightParagraphs(state, idx)
82
90
  level = state.level + 2
83
-
91
+
84
92
  i = idx + 2
85
93
  l = state.tokens.length
86
94
  while i < l
@@ -93,20 +101,48 @@ module MarkdownIt
93
101
  end
94
102
  end
95
103
 
96
-
97
104
  #------------------------------------------------------------------------------
98
105
  def self.list(state, startLine, endLine, silent)
106
+ isTerminatingParagraph = false
99
107
  tight = true
100
108
 
109
+ # if it's indented more than 3 spaces, it should be a code block
110
+ return false if (state.sCount[startLine] - state.blkIndent >= 4)
111
+
112
+ # limit conditions when list can interrupt
113
+ # a paragraph (validation mode only)
114
+ if silent && state.parentType == 'paragraph'
115
+ # Next list item should still terminate previous list item;
116
+ #
117
+ # This code can fail if plugins use blkIndent as well as lists,
118
+ # but I hope the spec gets fixed long before that happens.
119
+ #
120
+ if state.tShift[startLine] >= state.blkIndent
121
+ isTerminatingParagraph = true
122
+ end
123
+ end
124
+
101
125
  # Detect list type and position after marker
102
126
  if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0)
103
- isOrdered = true
127
+ isOrdered = true
128
+ start = state.bMarks[startLine] + state.tShift[startLine]
129
+ markerValue = state.src[start, posAfterMarker - start - 1].to_i
130
+
131
+ # If we're starting a new ordered list right after
132
+ # a paragraph, it should start with 1.
133
+ return false if isTerminatingParagraph && markerValue != 1
104
134
  elsif ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0)
105
135
  isOrdered = false
106
136
  else
107
137
  return false
108
138
  end
109
139
 
140
+ # If we're starting a new unordered list right after
141
+ # a paragraph, first line should not be empty.
142
+ if isTerminatingParagraph
143
+ return false if state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]
144
+ end
145
+
110
146
  # We should terminate list on style change. Remember first one to compare.
111
147
  markerCharCode = state.src.charCodeAt(posAfterMarker - 1)
112
148
 
@@ -139,15 +175,36 @@ module MarkdownIt
139
175
  prevEmptyEnd = false
140
176
  terminatorRules = state.md.block.ruler.getRules('list')
141
177
 
178
+ oldParentType = state.parentType
179
+ state.parentType = 'list'
180
+
142
181
  while (nextLine < endLine)
143
- contentStart = state.skipSpaces(posAfterMarker)
182
+ pos = posAfterMarker
144
183
  max = state.eMarks[nextLine]
145
184
 
185
+ initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine])
186
+
187
+ while pos < max
188
+ ch = state.src.charCodeAt(pos)
189
+
190
+ if ch == 0x09
191
+ offset += 4 - (offset + state.bsCount[nextLine]) % 4
192
+ elsif ch == 0x20
193
+ offset += 1
194
+ else
195
+ break
196
+ end
197
+
198
+ pos += 1
199
+ end
200
+
201
+ contentStart = pos
202
+
146
203
  if (contentStart >= max)
147
204
  # trimming space in "- \n 3" case, indent is 1 here
148
205
  indentAfterMarker = 1
149
206
  else
150
- indentAfterMarker = contentStart - posAfterMarker
207
+ indentAfterMarker = offset - initial
151
208
  end
152
209
 
153
210
  # If we have more than 4 spaces, the indent is 1
@@ -156,7 +213,7 @@ module MarkdownIt
156
213
 
157
214
  # " - test"
158
215
  # ^^^^^ - calculating total length of this thing
159
- indent = (posAfterMarker - state.bMarks[nextLine]) + indentAfterMarker
216
+ indent = initial + indentAfterMarker
160
217
 
161
218
  # Run subparser & write tokens
162
219
  token = state.push('list_item_open', 'li', 1)
@@ -166,13 +223,24 @@ module MarkdownIt
166
223
  oldIndent = state.blkIndent
167
224
  oldTight = state.tight
168
225
  oldTShift = state.tShift[startLine]
169
- oldParentType = state.parentType
170
- state.tShift[startLine] = contentStart - state.bMarks[startLine]
226
+ oldLIndent = state.sCount[startLine]
171
227
  state.blkIndent = indent
172
228
  state.tight = true
173
- state.parentType = 'list'
174
-
175
- state.md.block.tokenize(state, startLine, endLine, true)
229
+ state.tShift[startLine] = contentStart - state.bMarks[startLine]
230
+ state.sCount[startLine] = offset
231
+
232
+ if contentStart >= max && state.isEmpty(startLine + 1)
233
+ # workaround for this case
234
+ # (list item is empty, list terminates before "foo"):
235
+ # ~~~~~~~~
236
+ # -
237
+ #
238
+ # foo
239
+ # ~~~~~~~~
240
+ state.line = [state.line + 2, endLine].min
241
+ else
242
+ state.md.block.tokenize(state, startLine, endLine, true)
243
+ end
176
244
 
177
245
  # If any of list item is tight, mark list as tight
178
246
  if (!state.tight || prevEmptyEnd)
@@ -184,8 +252,8 @@ module MarkdownIt
184
252
 
185
253
  state.blkIndent = oldIndent
186
254
  state.tShift[startLine] = oldTShift
255
+ state.sCount[startLine] = oldLIndent
187
256
  state.tight = oldTight
188
- state.parentType = oldParentType
189
257
 
190
258
  token = state.push('list_item_close', 'li', -1)
191
259
  token.markup = markerCharCode.chr
@@ -195,12 +263,11 @@ module MarkdownIt
195
263
  contentStart = state.bMarks[startLine]
196
264
 
197
265
  break if (nextLine >= endLine)
198
- break if (state.isEmpty(nextLine))
199
266
 
200
267
  #
201
268
  # Try to check if list is terminated or continued.
202
269
  #
203
- break if (state.tShift[nextLine] < state.blkIndent)
270
+ break if (state.sCount[nextLine] < state.blkIndent)
204
271
 
205
272
  # fail if terminating block found
206
273
  terminate = false
@@ -224,7 +291,7 @@ module MarkdownIt
224
291
  break if (markerCharCode != state.src.charCodeAt(posAfterMarker - 1))
225
292
  end
226
293
 
227
- # Finilize list
294
+ # Finalize list
228
295
  if (isOrdered)
229
296
  token = state.push('ordered_list_close', 'ol', -1)
230
297
  else
@@ -235,6 +302,8 @@ module MarkdownIt
235
302
  listLines[1] = nextLine
236
303
  state.line = nextLine
237
304
 
305
+ state.parentType = oldParentType
306
+
238
307
  # mark paragraphs tight if needed
239
308
  if (tight)
240
309
  markTightParagraphs(state, listTokIdx)