motion-markdown-it 4.4.0 → 8.4.1

Sign up to get free protection for your applications and to get access to all the features.
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)