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
@@ -10,15 +10,18 @@ module MarkdownIt
10
10
  terminatorRules = state.md.block.ruler.getRules('paragraph')
11
11
  endLine = state.lineMax
12
12
 
13
+ oldParentType = state.parentType
14
+ state.parentType = 'paragraph'
15
+
13
16
  # jump line-by-line until empty one or EOF
14
17
  # for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
15
18
  while nextLine < endLine && !state.isEmpty(nextLine)
16
19
  # this would be a code block normally, but after paragraph
17
20
  # it's considered a lazy continuation regardless of what's there
18
- (nextLine += 1) && next if (state.tShift[nextLine] - state.blkIndent > 3)
21
+ (nextLine += 1) and next if (state.sCount[nextLine] - state.blkIndent > 3)
19
22
 
20
23
  # quirk for blockquotes, this line should already be checked by that rule
21
- (nextLine += 1) && next if state.tShift[nextLine] < 0
24
+ (nextLine += 1) and next if state.sCount[nextLine] < 0
22
25
 
23
26
  # Some tags can terminate paragraph without empty line.
24
27
  terminate = false
@@ -36,15 +39,17 @@ module MarkdownIt
36
39
 
37
40
  state.line = nextLine
38
41
 
39
- token = state.push('paragraph_open', 'p', 1)
40
- token.map = [ startLine, state.line ]
42
+ token = state.push('paragraph_open', 'p', 1)
43
+ token.map = [ startLine, state.line ]
44
+
45
+ token = state.push('inline', '', 0)
46
+ token.content = content
47
+ token.map = [ startLine, state.line ]
48
+ token.children = []
41
49
 
42
- token = state.push('inline', '', 0)
43
- token.content = content
44
- token.map = [ startLine, state.line ]
45
- token.children = []
50
+ token = state.push('paragraph_close', 'p', -1)
46
51
 
47
- token = state.push('paragraph_close', 'p', -1)
52
+ state.parentType = oldParentType
48
53
 
49
54
  return true
50
55
  end
@@ -1,10 +1,8 @@
1
1
  module MarkdownIt
2
2
  module RulesBlock
3
3
  class Reference
4
- extend Helpers::ParseLinkDestination
5
- extend Helpers::ParseLinkTitle
6
4
  extend Common::Utils
7
-
5
+
8
6
  #------------------------------------------------------------------------------
9
7
  def self.reference(state, startLine, _endLine, silent)
10
8
  lines = 0
@@ -12,7 +10,10 @@ module MarkdownIt
12
10
  max = state.eMarks[startLine]
13
11
  nextLine = startLine + 1
14
12
 
15
- return false if (state.src.charCodeAt(pos) != 0x5B) # [
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
+
16
+ return false if state.src.charCodeAt(pos) != 0x5B # [
16
17
 
17
18
  # Simple check to quickly interrupt scan on [link](url) at the start of line.
18
19
  # Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54
@@ -30,15 +31,18 @@ module MarkdownIt
30
31
  endLine = state.lineMax
31
32
 
32
33
  # jump line-by-line until empty one or EOF
33
- terminatorRules = state.md.block.ruler.getRules('reference')
34
+ terminatorRules = state.md.block.ruler.getRules('reference')
35
+
36
+ oldParentType = state.parentType
37
+ state.parentType = 'reference'
34
38
 
35
39
  while nextLine < endLine && !state.isEmpty(nextLine)
36
40
  # this would be a code block normally, but after paragraph
37
41
  # it's considered a lazy continuation regardless of what's there
38
- (nextLine += 1) && next if (state.tShift[nextLine] - state.blkIndent > 3)
42
+ (nextLine += 1) and next if (state.sCount[nextLine] - state.blkIndent > 3)
39
43
 
40
44
  # quirk for blockquotes, this line should already be checked by that rule
41
- (nextLine += 1) && next if state.tShift[nextLine] < 0
45
+ (nextLine += 1) and next if state.sCount[nextLine] < 0
42
46
 
43
47
  # Some tags can terminate paragraph without empty line.
44
48
  terminate = false
@@ -59,7 +63,7 @@ module MarkdownIt
59
63
  pos = 1
60
64
  while pos < max
61
65
  ch = str.charCodeAt(pos)
62
- if (ch == 0x5B ) # [
66
+ if (ch == 0x5B ) # [
63
67
  return false
64
68
  elsif (ch == 0x5D) # ]
65
69
  labelEnd = pos
@@ -84,7 +88,7 @@ module MarkdownIt
84
88
  ch = str.charCodeAt(pos)
85
89
  if (ch == 0x0A)
86
90
  lines += 1
87
- elsif (ch == 0x20)
91
+ elsif isSpace(ch)
88
92
  else
89
93
  break
90
94
  end
@@ -93,7 +97,7 @@ module MarkdownIt
93
97
 
94
98
  # [label]: destination 'title'
95
99
  # ^^^^^^^^^^^ parse this
96
- res = parseLinkDestination(str, pos, max)
100
+ res = state.md.helpers.parseLinkDestination(str, pos, max)
97
101
  return false if (!res[:ok])
98
102
 
99
103
  href = state.md.normalizeLink.call(res[:str])
@@ -113,7 +117,7 @@ module MarkdownIt
113
117
  ch = str.charCodeAt(pos)
114
118
  if (ch == 0x0A)
115
119
  lines += 1
116
- elsif (ch == 0x20)
120
+ elsif isSpace(ch)
117
121
  else
118
122
  break
119
123
  end
@@ -122,7 +126,7 @@ module MarkdownIt
122
126
 
123
127
  # [label]: destination 'title'
124
128
  # ^^^^^^^ parse this
125
- res = parseLinkTitle(str, pos, max)
129
+ res = state.md.helpers.parseLinkTitle(str, pos, max)
126
130
  if (pos < max && start != pos && res[:ok])
127
131
  title = res[:str]
128
132
  pos = res[:pos]
@@ -134,7 +138,9 @@ module MarkdownIt
134
138
  end
135
139
 
136
140
  # skip trailing spaces until the rest of the line
137
- while (pos < max && str.charCodeAt(pos) == 0x20) # space
141
+ while pos < max
142
+ ch = str.charCodeAt(pos)
143
+ break if !isSpace(ch)
138
144
  pos += 1
139
145
  end
140
146
 
@@ -145,7 +151,9 @@ module MarkdownIt
145
151
  title = ''
146
152
  pos = destEndPos
147
153
  lines = destEndLineNo
148
- while (pos < max && str.charCodeAt(pos) == 0x20) # space
154
+ while pos < max
155
+ ch = str.charCodeAt(pos)
156
+ break if !isSpace(ch)
149
157
  pos += 1
150
158
  end
151
159
  end
@@ -173,6 +181,8 @@ module MarkdownIt
173
181
  state.env[:references][label] = { title: title, href: href }
174
182
  end
175
183
 
184
+ state.parentType = oldParentType
185
+
176
186
  state.line = startLine + lines + 1
177
187
  return true
178
188
  end
@@ -3,11 +3,12 @@
3
3
  module MarkdownIt
4
4
  module RulesBlock
5
5
  class StateBlock
6
+ include MarkdownIt::Common::Utils
6
7
 
7
- attr_accessor :src, :md, :env, :tokens, :bMarks, :eMarks, :tShift
8
+ attr_accessor :src, :md, :env, :tokens, :bMarks, :eMarks, :tShift, :sCount, :bsCount
8
9
  attr_accessor :blkIndent, :line, :lineMax, :tight, :parentType, :ddIndent
9
10
  attr_accessor :level, :result
10
-
11
+
11
12
  #------------------------------------------------------------------------------
12
13
  def initialize(src, md, env, tokens)
13
14
  @src = src
@@ -22,7 +23,20 @@ module MarkdownIt
22
23
 
23
24
  @bMarks = [] # line begin offsets for fast jumps
24
25
  @eMarks = [] # line end offsets for fast jumps
25
- @tShift = [] # indent for each line
26
+ @tShift = [] # offsets of the first non-space characters (tabs not expanded)
27
+ @sCount = [] # indents for each line (tabs expanded)
28
+
29
+ # An amount of virtual spaces (tabs expanded) between beginning
30
+ # of each line (bMarks) and real beginning of that line.
31
+ #
32
+ # It exists only as a hack because blockquotes override bMarks
33
+ # losing information in the process.
34
+ #
35
+ # It's used only when expanding tabs, you can think about it as
36
+ # an initial tab length, e.g. bsCount=21 applied to string `\t123`
37
+ # means first tab should be expanded to 4-21%4 === 3 spaces.
38
+ #
39
+ @bsCount = []
26
40
 
27
41
  # block parser variables
28
42
  @blkIndent = 0 # required block content indent (for example, if we are in list)
@@ -32,6 +46,10 @@ module MarkdownIt
32
46
  @parentType = 'root' # if `list`, block parser stops on two newlines
33
47
  @ddIndent = -1 # indent of the current dd block (-1 if there isn't any)
34
48
 
49
+ # can be 'blockquote', 'list', 'root', 'paragraph' or 'reference'
50
+ # used in lists to determine if they interrupt a paragraph
51
+ @parentType = 'root'
52
+
35
53
  @level = 0
36
54
 
37
55
  # renderer
@@ -40,20 +58,23 @@ module MarkdownIt
40
58
  # Create caches
41
59
  # Generate markers.
42
60
  s = @src
43
- indent = 0
44
61
  indent_found = false
45
62
 
46
- start = pos = indent = 0
63
+ start = pos = indent = offset = 0
47
64
  len = s.length
48
- start.upto(len - 1) do |pos|
49
- # !!!!!!
50
- # for (start = pos = indent = 0, len = s.length pos < len pos++) {
65
+ while pos < len
51
66
  ch = s.charCodeAt(pos)
52
67
 
53
68
  if !indent_found
54
- if ch == 0x20 # space
69
+ if isSpace(ch)
55
70
  indent += 1
56
- next
71
+
72
+ if ch == 0x09
73
+ offset += 4 - offset % 4
74
+ else
75
+ offset += 1
76
+ end
77
+ (pos += 1) and next
57
78
  else
58
79
  indent_found = true
59
80
  end
@@ -64,17 +85,24 @@ module MarkdownIt
64
85
  @bMarks.push(start)
65
86
  @eMarks.push(pos)
66
87
  @tShift.push(indent)
88
+ @sCount.push(offset)
89
+ @bsCount.push(0)
67
90
 
68
91
  indent_found = false
69
92
  indent = 0
93
+ offset = 0
70
94
  start = pos + 1
71
95
  end
96
+
97
+ pos += 1
72
98
  end
73
99
 
74
100
  # Push fake entry to simplify cache bounds checks
75
101
  @bMarks.push(s.length)
76
102
  @eMarks.push(s.length)
77
103
  @tShift.push(0)
104
+ @sCount.push(0)
105
+ @bsCount.push(0)
78
106
 
79
107
  @lineMax = @bMarks.length - 1 # don't count last fake line
80
108
  end
@@ -112,12 +140,24 @@ module MarkdownIt
112
140
  def skipSpaces(pos)
113
141
  max = @src.length
114
142
  while pos < max
115
- break if @src.charCodeAt(pos) != 0x20 # space
143
+ ch = @src.charCodeAt(pos)
144
+ break if !isSpace(ch)
116
145
  pos += 1
117
146
  end
118
147
  return pos
119
148
  end
120
149
 
150
+ # Skip spaces from given position in reverse.
151
+ #------------------------------------------------------------------------------
152
+ def skipSpacesBack(pos, min)
153
+ return pos if pos <= min
154
+
155
+ while (pos > min)
156
+ return pos + 1 if !isSpace(@src.charCodeAt(pos -= 1))
157
+ end
158
+ return pos
159
+ end
160
+
121
161
  # Skip char codes from given position
122
162
  #------------------------------------------------------------------------------
123
163
  def skipChars(pos, code)
@@ -147,22 +187,12 @@ module MarkdownIt
147
187
 
148
188
  return '' if line_begin >= line_end
149
189
 
150
- # Opt: don't use push queue for single line
151
- if (line + 1) == line_end
152
- first = @bMarks[line] + [@tShift[line], indent].min
153
- last = @eMarks[line_end - 1] + (keepLastLF ? 1 : 0)
154
- return @src.slice(first...last)
155
- end
156
-
157
190
  queue = Array.new(line_end - line_begin)
158
191
 
159
192
  i = 0
160
193
  while line < line_end
161
- shift = @tShift[line]
162
- shift = indent if shift > indent
163
- shift = 0 if shift < 0
164
-
165
- first = @bMarks[line] + shift
194
+ lineIndent = 0
195
+ lineStart = first = @bMarks[line]
166
196
 
167
197
  if line + 1 < line_end || keepLastLF
168
198
  # No need for bounds check because we have fake entry on tail.
@@ -171,7 +201,32 @@ module MarkdownIt
171
201
  last = @eMarks[line]
172
202
  end
173
203
 
174
- queue[i] = @src.slice(first...last)
204
+ while first < last && lineIndent < indent
205
+ ch = @src.charCodeAt(first)
206
+
207
+ if isSpace(ch)
208
+ if ch === 0x09
209
+ lineIndent += 4 - (lineIndent + @bsCount[line]) % 4
210
+ else
211
+ lineIndent += 1
212
+ end
213
+ elsif first - lineStart < @tShift[line]
214
+ # patched tShift masked characters to look like spaces (blockquotes, list markers)
215
+ lineIndent += 1
216
+ else
217
+ break
218
+ end
219
+
220
+ first += 1
221
+ end
222
+
223
+ if lineIndent > indent
224
+ # partially expanding tabs in code blocks, e.g '\t\tfoobar'
225
+ # with indent=2 becomes ' \tfoobar'
226
+ queue[i] = (' ' * (lineIndent - indent)) + @src.slice(first...last)
227
+ else
228
+ queue[i] = @src.slice(first...last)
229
+ end
175
230
  line += 1
176
231
  i += 1
177
232
  end
@@ -3,6 +3,7 @@
3
3
  module MarkdownIt
4
4
  module RulesBlock
5
5
  class Table
6
+ extend Common::Utils
6
7
 
7
8
  #------------------------------------------------------------------------------
8
9
  def self.getLine(state, line)
@@ -21,17 +22,26 @@ module MarkdownIt
21
22
  lastPos = 0
22
23
  backTicked = false
23
24
  lastBackTick = 0
24
-
25
+
25
26
  ch = str.charCodeAt(pos)
26
27
 
27
28
  while (pos < max)
28
- if (ch == 0x60 && (escapes % 2 == 0)) # `
29
- backTicked = !backTicked
30
- lastBackTick = pos
29
+ if ch == 0x60 # `
30
+ if (backTicked)
31
+ # make \` close code sequence, but not open it;
32
+ # the reason is: `\` is correct code block
33
+ backTicked = false
34
+ lastBackTick = pos
35
+ elsif escapes % 2 == 0
36
+ backTicked = !backTicked
37
+ lastBackTick = pos
38
+ end
31
39
  elsif (ch == 0x7c && (escapes % 2 == 0) && !backTicked) # '|'
32
40
  result.push(str[lastPos...pos])
33
41
  lastPos = pos + 1
34
- elsif (ch == 0x5c) # '\'
42
+ end
43
+
44
+ if ch == 0x5c # '\'
35
45
  escapes += 1
36
46
  else
37
47
  escapes = 0
@@ -55,32 +65,44 @@ module MarkdownIt
55
65
 
56
66
  #------------------------------------------------------------------------------
57
67
  def self.table(state, startLine, endLine, silent)
58
- # should have at least three lines
68
+ # should have at least two lines
59
69
  return false if (startLine + 2 > endLine)
60
70
 
61
71
  nextLine = startLine + 1
62
72
 
63
- return false if (state.tShift[nextLine] < state.blkIndent)
73
+ return false if (state.sCount[nextLine] < state.blkIndent)
74
+
75
+ # if it's indented more than 3 spaces, it should be a code block
76
+ return false if state.sCount[nextLine] - state.blkIndent >= 4
77
+
78
+ # first character of the second line should be '|', '-', ':',
79
+ # and no other characters are allowed but spaces;
80
+ # basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
64
81
 
65
- # first character of the second line should be '|' or '-'
66
82
  pos = state.bMarks[nextLine] + state.tShift[nextLine]
67
83
  return false if (pos >= state.eMarks[nextLine])
68
84
 
69
85
  ch = state.src.charCodeAt(pos)
70
- return false if (ch != 0x7C && ch != 0x2D && ch != 0x3A) # != '|' && '-' && ':'
86
+ pos += 1
87
+ return false if (ch != 0x7C && ch != 0x2D && ch != 0x3A) # | or - or :
88
+
89
+ while pos < state.eMarks[nextLine]
90
+ ch = state.src.charCodeAt(pos)
91
+ return false if (ch != 0x7C && ch != 0x2D && ch != 0x3A && !isSpace(ch)) # | or - or :
92
+
93
+ pos += 1
94
+ end
71
95
 
72
96
  lineText = getLine(state, startLine + 1)
73
- return false if (/^[-:| ]+$/ =~ lineText).nil?
74
97
 
75
- rows = lineText.split('|')
76
- return false if (rows.length < 2)
98
+ columns = lineText.split('|')
77
99
  aligns = []
78
- (0...rows.length).each do |i|
79
- t = rows[i].strip
100
+ (0...columns.length).each do |i|
101
+ t = columns[i].strip
80
102
  if t.empty?
81
103
  # allow empty columns before and after table, but not in between columns
82
104
  # e.g. allow ` |---| `, disallow ` ---||--- `
83
- if (i == 0 || i == rows.length - 1)
105
+ if (i == 0 || i == columns.length - 1)
84
106
  next
85
107
  else
86
108
  return false
@@ -99,8 +121,14 @@ module MarkdownIt
99
121
 
100
122
  lineText = getLine(state, startLine).strip
101
123
  return false if !lineText.include?('|')
102
- rows = self.escapedSplit(lineText.gsub(/^\||\|$/, ''))
103
- return false if (aligns.length != rows.length)
124
+ return false if state.sCount[startLine] - state.blkIndent >= 4
125
+ columns = self.escapedSplit(lineText.gsub(/^\||\|$/, ''))
126
+
127
+ # header row will define an amount of columns in the entire table,
128
+ # and align row shouldn't be smaller than that (the rest of the rows can)
129
+ columnCount = columns.length
130
+ return false if columnCount > aligns.length
131
+
104
132
  return true if silent
105
133
 
106
134
  token = state.push('table_open', 'table', 1)
@@ -112,7 +140,7 @@ module MarkdownIt
112
140
  token = state.push('tr_open', 'tr', 1)
113
141
  token.map = [ startLine, startLine + 1 ]
114
142
 
115
- (0...rows.length).each do |i|
143
+ (0...columns.length).each do |i|
116
144
  token = state.push('th_open', 'th', 1)
117
145
  token.map = [ startLine, startLine + 1 ]
118
146
  unless aligns[i].empty?
@@ -120,7 +148,7 @@ module MarkdownIt
120
148
  end
121
149
 
122
150
  token = state.push('inline', '', 0)
123
- token.content = rows[i].strip
151
+ token.content = columns[i].strip
124
152
  token.map = [ startLine, startLine + 1 ]
125
153
  token.children = []
126
154
 
@@ -135,24 +163,22 @@ module MarkdownIt
135
163
 
136
164
  nextLine = startLine + 2
137
165
  while nextLine < endLine
138
- break if (state.tShift[nextLine] < state.blkIndent)
166
+ break if (state.sCount[nextLine] < state.blkIndent)
139
167
 
140
168
  lineText = getLine(state, nextLine).strip
141
169
  break if !lineText.include?('|')
142
- rows = self.escapedSplit(lineText.gsub(/^\||\|$/, ''))
143
-
144
- # set number of columns to number of columns in header row
145
- rows_length = aligns.length
170
+ break if state.sCount[nextLine] - state.blkIndent >= 4
171
+ columns = self.escapedSplit(lineText.gsub(/^\||\|$/, ''))
146
172
 
147
173
  token = state.push('tr_open', 'tr', 1)
148
- (0...rows_length).each do |i|
174
+ (0...columnCount).each do |i|
149
175
  token = state.push('td_open', 'td', 1)
150
176
  unless aligns[i].empty?
151
177
  token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]
152
178
  end
153
179
 
154
180
  token = state.push('inline', '', 0)
155
- token.content = rows[i] ? rows[i].strip : ''
181
+ token.content = columns[i] ? columns[i].strip : ''
156
182
  token.children = []
157
183
 
158
184
  token = state.push('td_close', 'td', -1)