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