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  }