1package main
2
3import (
4	"errors"
5
6	"github.com/xyproto/textoutput"
7	"github.com/xyproto/vt100"
8)
9
10var (
11	colorSlice = make([]vt100.AttributeColor, 0) // to be pushed to and popped from
12
13	// the first color in this slice will normally not be used until the parenthesis are many levels deep,
14	// the second one will be used for the regular case which is 1 level deep
15	rainbowParenColors = []vt100.AttributeColor{vt100.LightMagenta, vt100.LightRed, vt100.Yellow, vt100.LightYellow, vt100.LightGreen, vt100.LightBlue}
16
17	// the color for unmatched parenthesis
18	unmatchedParenColor = vt100.White
19
20	errUnmatchedParenthesis = errors.New("unmatched parenthesis")
21)
22
23// rainbowParen implements "rainbow parenthesis" which colors "(" and ")" according to how deep they are nested
24// pCount is the existing parenthesis count when reaching the start of this line
25func (e *Editor) rainbowParen(parCount, braCount *int, chars *[]textoutput.CharAttribute, singleLineCommentMarker string, ignoreSingleQuotes bool) (err error) {
26	var (
27		prevPrevRune = '\n'
28
29		// CharAttribute has a rune "R" and a vt100.AttributeColor "A"
30		nextChar = textoutput.CharAttribute{R: '\n', A: e.Background}
31		prevChar = textoutput.CharAttribute{R: '\n', A: e.Background}
32
33		lastColor = rainbowParenColors[len(rainbowParenColors)-1]
34	)
35
36	q, qerr := NewQuoteState(singleLineCommentMarker, e.mode, ignoreSingleQuotes)
37	if qerr != nil {
38		return qerr
39	}
40
41	// Initialize the quote state parenthesis count with the one that is for the beginning of this line, in the current document
42	q.parCount = *parCount // parenthesis count
43	q.braCount = *braCount // bracket count
44
45	for i, char := range *chars {
46
47		q.ProcessRune(char.R, prevChar.R, prevPrevRune)
48		prevPrevRune = prevChar.R
49
50		if !q.None() {
51			// Skip comments and strings
52			continue
53		}
54
55		// Get the next rune and attribute
56		if (i + 1) < len(*chars) {
57			nextChar.R = (*chars)[i+1].R
58			nextChar.A = (*chars)[i+1].A
59		}
60
61		// Get the previous rune and attribute
62		if i > 0 {
63			prevChar.R = (*chars)[i-1].R
64			prevChar.A = (*chars)[i-1].A
65		}
66
67		// Count parenthesis
68		*parCount = q.parCount
69		// Count square brackets
70		*braCount = q.braCount
71
72		openingP := false // parenthesis
73		openingB := false // bracket
74		switch char.R {
75		case '(':
76			openingP = true
77		case '[':
78			openingB = true
79		case ')':
80		// openingP is already set to false, for this case
81		// openingP = false
82		case ']':
83			// Don't continue the loop, continue below
84		default:
85			// Not an opening or closing parenthesis or square bracket
86			continue
87		}
88
89		if *parCount < 0 || *braCount < 0 {
90			// Too many closing parenthesis or brackets!
91			char.A = unmatchedParenColor
92			err = errUnmatchedParenthesis
93		} else if openingB || openingP {
94			// Select a color, using modulo
95			// Select another color if it's the same as the text that follows
96			selected := (*braCount + *parCount) % len(rainbowParenColors)
97			char.A = rainbowParenColors[selected]
98			// If the character before ( or ) are ' ' or '\t' OR the index is 0, color it with the last color in rainbowParenColors
99			if prevChar.R == ' ' || prevChar.R == '\t' || i == 0 {
100				char.A = lastColor
101			} else {
102				// Loop until a color that is not the same as the color of the next character is selected
103				// (and the next rune is not blank or end of line)
104				for (char.A.Equal(nextChar.A) && nextChar.R != ' ' && nextChar.R != '\t' && nextChar.R != '(' && nextChar.R != ')' && nextChar.R != '[' && nextChar.R != ']') || (char.A.Equal(prevChar.A) && prevChar.R != ' ' && prevChar.R != '\t' && prevChar.R != '(' && prevChar.R != ')' && prevChar.R != '[' && prevChar.R != ']') {
105					selected++
106					if selected >= len(rainbowParenColors) {
107						selected = 0
108					}
109					char.A = rainbowParenColors[selected]
110				}
111			}
112			// Push the color to the color stack
113			colorSlice = append(colorSlice, char.A)
114		} else {
115			if len(colorSlice) > 0 {
116				// pop the color from the color stack
117				lastIndex := len(colorSlice) - 1
118				char.A = colorSlice[lastIndex]
119				colorSlice = colorSlice[:lastIndex]
120			} else {
121				char.A = lastColor
122			}
123		}
124
125		// For debugging
126		//s := strconv.Itoa(*parCount)
127		//if len(s) == 1 {
128		//	char.R = []rune(s)[0]
129		//} else {
130		//	char.R = []rune(s)[1]
131		//}
132
133		// Keep the rune, but use the new AttributeColor
134		(*chars)[i] = char
135	}
136	return
137}
138