]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/highlight/highlighter.go
Fix out of bounds error on syntax highlighting
[micro.git] / cmd / micro / highlight / highlighter.go
index ed312f0fdb44ac1fd4c4f3c6da60fccaffb3c582..ab3eb15cc1af2c47c6e4aaa61bb346fd5e979047 100644 (file)
@@ -3,10 +3,23 @@ package highlight
 import (
        "regexp"
        "strings"
+       "unicode/utf8"
 
        "github.com/dlclark/regexp2"
 )
 
+// RunePos returns the rune index of a given byte index
+// This could cause problems if the byte index is between code points
+func runePos(p int, str string) int {
+       if p < 0 {
+               return 0
+       }
+       if p >= len(str) {
+               return utf8.RuneCountInString(str)
+       }
+       return utf8.RuneCountInString(str[:p])
+}
+
 func combineLineMatch(src, dst LineMatch) LineMatch {
        for k, v := range src {
                if g, ok := dst[k]; ok {
@@ -25,7 +38,8 @@ type State *Region
 
 // LineStates is an interface for a buffer-like object which can also store the states and matches for every line
 type LineStates interface {
-       LineData() [][]byte
+       Line(n int) string
+       LinesNum() int
        State(lineN int) State
        SetState(lineN int, s State)
        SetMatch(lineN int, m LineMatch)
@@ -79,37 +93,40 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
                        return nil
                }
        }
-       return regex.FindAllIndex(str, -1)
+       return regex.FindAllIndex([]byte(string(str)), -1)
 }
 
-func (h *Highlighter) highlightRegion(start int, canMatchEnd bool, lineNum int, line []byte, region *Region) LineMatch {
-       fullHighlights := make([]uint8, len([]rune(string(line))))
-       for i := 0; i < len(fullHighlights); i++ {
-               fullHighlights[i] = region.group
-       }
-
-       highlights := make(LineMatch)
+func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, region *Region, statesOnly bool) LineMatch {
+       // highlights := make(LineMatch)
 
        if start == 0 {
-               highlights[0] = region.group
+               if !statesOnly {
+                       highlights[0] = region.group
+               }
        }
 
        loc := findIndex(region.end, line, start == 0, canMatchEnd)
        if loc != nil {
-               highlights[start+loc[1]-1] = region.group
+               if !statesOnly {
+                       highlights[start+runePos(loc[1]-1, string(line))] = region.group
+               }
                if region.parent == nil {
-                       highlights[start+loc[1]] = 0
-                       return combineLineMatch(highlights,
-                               combineLineMatch(h.highlightRegion(start, false, lineNum, line[:loc[0]], region),
-                                       h.highlightEmptyRegion(start+loc[1], canMatchEnd, lineNum, line[loc[1]:])))
+                       if !statesOnly {
+                               highlights[start+runePos(loc[1], string(line))] = 0
+                               h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], region, statesOnly)
+                       }
+                       h.highlightEmptyRegion(highlights, start+runePos(loc[1], string(line)), canMatchEnd, lineNum, line[loc[1]:], statesOnly)
+                       return highlights
+               }
+               if !statesOnly {
+                       highlights[start+runePos(loc[1], string(line))] = region.parent.group
+                       h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], region, statesOnly)
                }
-               highlights[start+loc[1]] = region.parent.group
-               return combineLineMatch(highlights,
-                       combineLineMatch(h.highlightRegion(start, false, lineNum, line[:loc[0]], region),
-                               h.highlightRegion(start+loc[1], canMatchEnd, lineNum, line[loc[1]:], region.parent)))
+               h.highlightRegion(highlights, start+runePos(loc[1], string(line)), canMatchEnd, lineNum, line[loc[1]:], region.parent, statesOnly)
+               return highlights
        }
 
-       if len(line) == 0 {
+       if len(line) == 0 || statesOnly {
                if canMatchEnd {
                        h.lastRegion = region
                }
@@ -129,10 +146,15 @@ func (h *Highlighter) highlightRegion(start int, canMatchEnd bool, lineNum int,
                }
        }
        if firstLoc[0] != len(line) {
-               highlights[start+firstLoc[0]] = firstRegion.group
-               return combineLineMatch(highlights,
-                       combineLineMatch(h.highlightRegion(start, false, lineNum, line[:firstLoc[0]], region),
-                               h.highlightRegion(start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion)))
+               highlights[start+runePos(firstLoc[0], string(line))] = firstRegion.group
+               h.highlightRegion(highlights, start, false, lineNum, line[:firstLoc[0]], region, statesOnly)
+               h.highlightRegion(highlights, start+runePos(firstLoc[1], string(line)), canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
+               return highlights
+       }
+
+       fullHighlights := make([]uint8, len(line))
+       for i := 0; i < len(fullHighlights); i++ {
+               fullHighlights[i] = region.group
        }
 
        for _, p := range region.rules.patterns {
@@ -145,8 +167,8 @@ func (h *Highlighter) highlightRegion(start int, canMatchEnd bool, lineNum int,
        }
        for i, h := range fullHighlights {
                if i == 0 || h != fullHighlights[i-1] {
-                       if _, ok := highlights[start+i]; !ok {
-                               highlights[start+i] = h
+                       if _, ok := highlights[start+runePos(i, string(line))]; !ok {
+                               highlights[start+runePos(i, string(line))] = h
                        }
                }
        }
@@ -158,9 +180,7 @@ func (h *Highlighter) highlightRegion(start int, canMatchEnd bool, lineNum int,
        return highlights
 }
 
-func (h *Highlighter) highlightEmptyRegion(start int, canMatchEnd bool, lineNum int, line []byte) LineMatch {
-       fullHighlights := make([]uint8, len(line))
-       highlights := make(LineMatch)
+func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
        if len(line) == 0 {
                if canMatchEnd {
                        h.lastRegion = nil
@@ -180,12 +200,23 @@ func (h *Highlighter) highlightEmptyRegion(start int, canMatchEnd bool, lineNum
                }
        }
        if firstLoc[0] != len(line) {
-               highlights[start+firstLoc[0]] = firstRegion.group
-               return combineLineMatch(highlights,
-                       combineLineMatch(h.highlightEmptyRegion(start, false, lineNum, line[:firstLoc[0]]),
-                               h.highlightRegion(start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion)))
+               if !statesOnly {
+                       highlights[start+runePos(firstLoc[0], string(line))] = firstRegion.group
+               }
+               h.highlightEmptyRegion(highlights, start, false, lineNum, line[:firstLoc[0]], statesOnly)
+               h.highlightRegion(highlights, start+runePos(firstLoc[1], string(line)), canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
+               return highlights
        }
 
+       if statesOnly {
+               if canMatchEnd {
+                       h.lastRegion = nil
+               }
+
+               return highlights
+       }
+
+       fullHighlights := make([]uint8, len(line))
        for _, p := range h.def.rules.patterns {
                matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
                for _, m := range matches {
@@ -196,8 +227,8 @@ func (h *Highlighter) highlightEmptyRegion(start int, canMatchEnd bool, lineNum
        }
        for i, h := range fullHighlights {
                if i == 0 || h != fullHighlights[i-1] {
-                       if _, ok := highlights[start+i]; !ok {
-                               highlights[start+i] = h
+                       if _, ok := highlights[start+runePos(i, string(line))]; !ok {
+                               highlights[start+runePos(i, string(line))] = h
                        }
                }
        }
@@ -219,11 +250,12 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
 
        for i := 0; i < len(lines); i++ {
                line := []byte(lines[i])
+               highlights := make(LineMatch)
 
                if i == 0 || h.lastRegion == nil {
-                       lineMatches = append(lineMatches, h.highlightEmptyRegion(0, true, i, line))
+                       lineMatches = append(lineMatches, h.highlightEmptyRegion(highlights, 0, true, i, line, false))
                } else {
-                       lineMatches = append(lineMatches, h.highlightRegion(0, true, i, line, h.lastRegion))
+                       lineMatches = append(lineMatches, h.highlightRegion(highlights, 0, true, i, line, h.lastRegion, false))
                }
        }
 
@@ -232,15 +264,14 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
 
 // HighlightStates correctly sets all states for the buffer
 func (h *Highlighter) HighlightStates(input LineStates) {
-       lines := input.LineData()
-
-       for i := 0; i < len(lines); i++ {
-               line := []byte(lines[i])
+       for i := 0; i < input.LinesNum(); i++ {
+               line := []byte(input.Line(i))
+               // highlights := make(LineMatch)
 
                if i == 0 || h.lastRegion == nil {
-                       h.highlightEmptyRegion(0, true, i, line)
+                       h.highlightEmptyRegion(nil, 0, true, i, line, true)
                } else {
-                       h.highlightRegion(0, true, i, line, h.lastRegion)
+                       h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
                }
 
                curState := h.lastRegion
@@ -253,43 +284,43 @@ func (h *Highlighter) HighlightStates(input LineStates) {
 // It sets all other matches in the buffer to nil to conserve memory
 // This assumes that all the states are set correctly
 func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
-       lines := input.LineData()
+       for i := startline; i < endline; i++ {
+               if i >= input.LinesNum() {
+                       break
+               }
 
-       for i := 0; i < len(lines); i++ {
-               if i >= startline && i < endline {
-                       line := []byte(lines[i])
-
-                       var match LineMatch
-                       if i == 0 || input.State(i-1) == nil {
-                               match = h.highlightEmptyRegion(0, true, i, line)
-                       } else {
-                               match = h.highlightRegion(0, true, i, line, input.State(i-1))
-                       }
+               line := []byte(input.Line(i))
+               highlights := make(LineMatch)
 
-                       input.SetMatch(i, match)
+               var match LineMatch
+               if i == 0 || input.State(i-1) == nil {
+                       match = h.highlightEmptyRegion(highlights, 0, true, i, line, false)
                } else {
-                       input.SetMatch(i, nil)
+                       match = h.highlightRegion(highlights, 0, true, i, line, input.State(i-1), false)
                }
+
+               input.SetMatch(i, match)
        }
 }
 
 // ReHighlightStates will scan down from `startline` and set the appropriate end of line state
 // for each line until it comes across the same state in two consecutive lines
 func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
-       lines := input.LineData()
+       // lines := input.LineData()
 
        h.lastRegion = nil
        if startline > 0 {
                h.lastRegion = input.State(startline - 1)
        }
-       for i := startline; i < len(lines); i++ {
-               line := []byte(lines[i])
+       for i := startline; i < input.LinesNum(); i++ {
+               line := []byte(input.Line(i))
+               // highlights := make(LineMatch)
 
                // var match LineMatch
                if i == 0 || h.lastRegion == nil {
-                       h.highlightEmptyRegion(0, true, i, line)
+                       h.highlightEmptyRegion(nil, 0, true, i, line, true)
                } else {
-                       h.highlightRegion(0, true, i, line, h.lastRegion)
+                       h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
                }
                curState := h.lastRegion
                lastState := input.State(i)
@@ -304,9 +335,8 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
 
 // ReHighlightLine will rehighlight the state and match for a single line
 func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
-       lines := input.LineData()
-
-       line := []byte(lines[lineN])
+       line := []byte(input.Line(lineN))
+       highlights := make(LineMatch)
 
        h.lastRegion = nil
        if lineN > 0 {
@@ -315,9 +345,9 @@ func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
 
        var match LineMatch
        if lineN == 0 || h.lastRegion == nil {
-               match = h.highlightEmptyRegion(0, true, lineN, line)
+               match = h.highlightEmptyRegion(highlights, 0, true, lineN, line, false)
        } else {
-               match = h.highlightRegion(0, true, lineN, line, h.lastRegion)
+               match = h.highlightRegion(highlights, 0, true, lineN, line, h.lastRegion, false)
        }
        curState := h.lastRegion