-
Notifications
You must be signed in to change notification settings - Fork 1
/
codeblock.go
181 lines (143 loc) · 4.04 KB
/
codeblock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package md
import (
"bytes"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
var fencedCodeBlockInfoKey = parser.NewContextKey()
type fenced struct{}
type fenceData struct {
length int
node ast.Node
}
func (b fenced) Trigger() []byte {
return []byte{'`'}
}
func (b fenced) Parse(p ast.Node, r text.Reader, pc parser.Context) ast.Node {
n, _ := b.open(p, r, pc)
if n == nil {
return nil
}
// Crawl until b.next is done:
for state := parser.Continue; state != parser.Close; state = b.next(n, r, pc) {
}
// Close:
b.close(n, r, pc)
return n
}
func (b fenced) open(p ast.Node, r text.Reader, pc parser.Context) (ast.Node, parser.State) {
line, segment := r.PeekLine()
i := 0
for ; i < len(line) && line[i] == '`'; i++ {
}
oFenceLength := i
// If there are less than 3 backticks:
if oFenceLength < 3 {
return nil, parser.NoChildren
}
// Advance through the backticks
r.Advance(oFenceLength)
node := ast.NewFencedCodeBlock(nil)
// If this isn't the last thing in the line: (```<language>)
if i < len(line)-1 {
rest := line[i:]
infoStart, infoStop := segment.Start-segment.Padding+i, segment.Stop
if len(rest) > 0 && infoStart < infoStop && bytes.IndexByte(rest, '\n') > -1 {
// Trim trailing whitespaces:
left := util.TrimLeftSpaceLength(rest)
right := util.TrimRightSpaceLength(rest)
// If there is no space:
if left < right && bytes.IndexByte(rest, ' ') == -1 {
seg := text.NewSegment(infoStart+left, infoStop-right)
node.Info = ast.NewTextSegment(seg)
r.Advance(infoStop - infoStart)
}
}
} else {
// Else consume the rest of the line.
r.AdvanceLine()
}
pc.Set(fencedCodeBlockInfoKey, &fenceData{oFenceLength, node})
return node, parser.NoChildren
}
func (b fenced) next(node ast.Node, r text.Reader, pc parser.Context) parser.State {
line, segment := r.PeekLine()
if len(line) == 0 {
return parser.Close
}
fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
_, pos := util.IndentWidth(line, r.LineOffset())
// Crawl i to ```
i := pos
for ; i < len(line) && line[i] != '`'; i++ {
}
// Is there a string literal? Write it.
pos, padding := util.DedentPositionPadding(line, r.LineOffset(), segment.Padding, 0)
// start+i accounts for everything before end (```)
var start, stop = segment.Start + pos, segment.Start + i
// Since we're assigning this segment a Start, IsEmpty() would fail if
// seg.End is not touched.
var seg = text.Segment{
Start: start,
Stop: stop,
Padding: padding,
}
r.AdvanceAndSetPadding(stop-start, padding)
defer func() {
// Append this at the end of the function, as the block below might
// reuse our text segment.
node.Lines().Append(seg)
}()
// If found:
if i != len(line) {
// Update the starting position:
pos = i
// Iterate until we're out of backticks:
for ; i < len(line) && line[i] == '`'; i++ {
}
// Do we have enough (3 or more) backticks?
// If yes, end the codeblock properly.
if length := i - pos; length >= fdata.length {
r.Advance(length)
return parser.Close
} else {
// No, treat the rest as text:
seg.Stop = segment.Stop
r.Advance(segment.Stop - stop)
}
}
return parser.Continue | parser.NoChildren
}
func (b fenced) close(node ast.Node, r text.Reader, pc parser.Context) {
fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
if fdata.node == node {
pc.Set(fencedCodeBlockInfoKey, nil)
}
lines := node.Lines()
if length := lines.Len(); length > 0 {
// Trim first whitespace
first := lines.At(0)
lines.Set(0, first.TrimLeftSpace(r.Source()))
// Trim last new line
last := lines.At(length - 1)
if last.Len() == 0 {
lines.SetSliced(0, length-1)
length--
}
// If we've sliced everything away.
if length == 0 {
return
}
// Trim the new last line's trailing whitespace
last = lines.At(length - 1)
lines.Set(length-1, last.TrimRightSpace(r.Source()))
}
}
func (b fenced) CanInterruptParagraph() bool {
return true
}
func (b fenced) CanAcceptIndentedLine() bool {
return false
}