1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package markdown
5
6import (
7	"strings"
8)
9
10type FencedCodeLine struct {
11	Indentation int
12	Range       Range
13}
14
15type FencedCode struct {
16	blockBase
17	markdown           string
18	didSeeClosingFence bool
19
20	Indentation  int
21	OpeningFence Range
22	RawInfo      Range
23	RawCode      []FencedCodeLine
24}
25
26func (b *FencedCode) Code() (result string) {
27	for _, code := range b.RawCode {
28		result += strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End]
29	}
30	return
31}
32
33func (b *FencedCode) Info() string {
34	return Unescape(b.markdown[b.RawInfo.Position:b.RawInfo.End])
35}
36
37func (b *FencedCode) Continuation(indentation int, r Range) *continuation {
38	if b.didSeeClosingFence {
39		return nil
40	}
41	return &continuation{
42		Indentation: indentation,
43		Remaining:   r,
44	}
45}
46
47func (b *FencedCode) AddLine(indentation int, r Range) bool {
48	s := b.markdown[r.Position:r.End]
49	if indentation <= 3 && strings.HasPrefix(s, b.markdown[b.OpeningFence.Position:b.OpeningFence.End]) {
50		suffix := strings.TrimSpace(s[b.OpeningFence.End-b.OpeningFence.Position:])
51		isClosingFence := true
52		for _, c := range suffix {
53			if c != rune(s[0]) {
54				isClosingFence = false
55				break
56			}
57		}
58		if isClosingFence {
59			b.didSeeClosingFence = true
60			return true
61		}
62	}
63
64	if indentation >= b.Indentation {
65		indentation -= b.Indentation
66	} else {
67		indentation = 0
68	}
69
70	b.RawCode = append(b.RawCode, FencedCodeLine{
71		Indentation: indentation,
72		Range:       r,
73	})
74	return true
75}
76
77func (b *FencedCode) AllowsBlockStarts() bool {
78	return false
79}
80
81func fencedCodeStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
82	s := markdown[r.Position:r.End]
83
84	if !strings.HasPrefix(s, "```") && !strings.HasPrefix(s, "~~~") {
85		return nil
86	}
87
88	fenceCharacter := rune(s[0])
89	fenceLength := 3
90	for _, c := range s[3:] {
91		if c == fenceCharacter {
92			fenceLength++
93		} else {
94			break
95		}
96	}
97
98	for i := r.Position + fenceLength; i < r.End; i++ {
99		if markdown[i] == '`' {
100			return nil
101		}
102	}
103
104	return []Block{
105		&FencedCode{
106			markdown:     markdown,
107			Indentation:  indentation,
108			RawInfo:      trimRightSpace(markdown, Range{r.Position + fenceLength, r.End}),
109			OpeningFence: Range{r.Position, r.Position + fenceLength},
110		},
111	}
112}
113