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