X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fhighlight%2Fhighlighter.go;h=2a7634612a70731538b9046465b1b66141daf01c;hb=342f3c223da0df38d9c4f79f4a8175c5c032adf0;hp=20c3466aecb0d865c5becbe4a6dc485d247f2e1a;hpb=f637268fa7d6aa77c29a9869fb2249f3b0fd6b7f;p=micro.git diff --git a/cmd/micro/highlight/highlighter.go b/cmd/micro/highlight/highlighter.go index 20c3466a..2a763461 100644 --- a/cmd/micro/highlight/highlighter.go +++ b/cmd/micro/highlight/highlighter.go @@ -3,12 +3,25 @@ package highlight import ( "regexp" "strings" + "unicode/utf8" ) +// 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 { - if g == "" { + if g == 0 { dst[k] = v } } else { @@ -18,29 +31,36 @@ func combineLineMatch(src, dst LineMatch) LineMatch { return dst } -type State *Region +// A State represents the region at the end of a line +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) } +// A Highlighter contains the information needed to highlight a string type Highlighter struct { - lastRegion *Region - def *Def + lastRegion *region + Def *Def } +// NewHighlighter returns a new highlighter from the given syntax definition func NewHighlighter(def *Def) *Highlighter { h := new(Highlighter) - h.def = def + h.Def = def return h } -type LineMatch map[int]string +// LineMatch represents the syntax highlighting matches for one line. Each index where the coloring is changed is marked with that +// color's group (represented as one byte) +type LineMatch map[int]Group -func FindIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) []int { +func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) []int { regexStr := regex.String() if strings.Contains(regexStr, "^") { if !canMatchStart { @@ -52,10 +72,26 @@ func FindIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool return nil } } - return regex.FindIndex(str) + + var strbytes []byte + if skip != nil { + strbytes = skip.ReplaceAllFunc(strbytes, func(match []byte) []byte { + res := make([]byte, utf8.RuneCount(match)) + return res + }) + } else { + strbytes = []byte(string(str)) + } + + match := regex.FindIndex(strbytes) + if match == nil { + return nil + } + // return []int{match.Index, match.Index + match.Length} + return []int{runePos(match[0], string(str)), runePos(match[1], string(str))} } -func FindAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int { +func findAllIndex(regex *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) [][]int { regexStr := regex.String() if strings.Contains(regexStr, "^") { if !canMatchStart { @@ -67,38 +103,57 @@ func FindAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b return nil } } - return regex.FindAllIndex(str, -1) + matches := regex.FindAllIndex([]byte(string(str)), -1) + for i, m := range matches { + matches[i][0] = runePos(m[0], string(str)) + matches[i][1] = runePos(m[1], string(str)) + } + return matches } -func (h *Highlighter) highlightRegion(start int, canMatchEnd bool, lineNum int, line []byte, region *Region) LineMatch { - highlights := make(LineMatch) +func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, curRegion *region, statesOnly bool) LineMatch { + // highlights := make(LineMatch) - loc := FindIndex(region.end, line, start == 0, canMatchEnd) + if start == 0 { + if !statesOnly { + highlights[0] = curRegion.group + } + } + + loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd) if loc != nil { - if region.parent == nil { - highlights[start+loc[1]] = "" - 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+loc[1]-1] = curRegion.group + } + if curRegion.parent == nil { + if !statesOnly { + highlights[start+loc[1]] = 0 + h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly) + } + h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], statesOnly) + return highlights } - 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))) + if !statesOnly { + highlights[start+loc[1]] = curRegion.parent.group + h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly) + } + h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], curRegion.parent, statesOnly) + return highlights } - if len(line) == 0 { + if len(line) == 0 || statesOnly { if canMatchEnd { - h.lastRegion = region + h.lastRegion = curRegion } return highlights } firstLoc := []int{len(line), 0} - var firstRegion *Region - for _, r := range region.rules.regions { - loc := FindIndex(r.start, line, start == 0, canMatchEnd) + + var firstRegion *region + for _, r := range curRegion.rules.regions { + loc := findIndex(r.start, nil, line, start == 0, canMatchEnd) if loc != nil { if loc[0] < firstLoc[0] { firstLoc = loc @@ -108,30 +163,40 @@ 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))) + h.highlightRegion(highlights, start, false, lineNum, line[:firstLoc[0]], curRegion, statesOnly) + h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly) + return highlights } - for _, p := range region.rules.patterns { - matches := FindAllIndex(p.regex, line, start == 0, canMatchEnd) + fullHighlights := make([]Group, len([]rune(string(line)))) + for i := 0; i < len(fullHighlights); i++ { + fullHighlights[i] = curRegion.group + } + + for _, p := range curRegion.rules.patterns { + matches := findAllIndex(p.regex, line, start == 0, canMatchEnd) for _, m := range matches { - highlights[start+m[0]] = p.group - if _, ok := highlights[start+m[1]]; !ok { - highlights[start+m[1]] = region.group + for i := m[0]; i < m[1]; i++ { + fullHighlights[i] = p.group + } + } + } + for i, h := range fullHighlights { + if i == 0 || h != fullHighlights[i-1] { + if _, ok := highlights[start+i]; !ok { + highlights[start+i] = h } } } if canMatchEnd { - h.lastRegion = region + h.lastRegion = curRegion } return highlights } -func (h *Highlighter) highlightEmptyRegion(start int, canMatchEnd bool, lineNum int, line []byte) LineMatch { - highlights := make(LineMatch) +func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, statesOnly bool) LineMatch { if len(line) == 0 { if canMatchEnd { h.lastRegion = nil @@ -140,9 +205,9 @@ func (h *Highlighter) highlightEmptyRegion(start int, canMatchEnd bool, lineNum } firstLoc := []int{len(line), 0} - var firstRegion *Region - for _, r := range h.def.rules.regions { - loc := FindIndex(r.start, line, start == 0, canMatchEnd) + var firstRegion *region + for _, r := range h.Def.rules.regions { + loc := findIndex(r.start, nil, line, start == 0, canMatchEnd) if loc != nil { if loc[0] < firstLoc[0] { firstLoc = loc @@ -151,18 +216,35 @@ 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+firstLoc[0]] = firstRegion.group + } + h.highlightEmptyRegion(highlights, start, false, lineNum, line[:firstLoc[0]], statesOnly) + h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly) + return highlights + } + + if statesOnly { + if canMatchEnd { + h.lastRegion = nil + } + + return highlights } - for _, p := range h.def.rules.patterns { - matches := FindAllIndex(p.regex, line, start == 0, canMatchEnd) + fullHighlights := make([]Group, len(line)) + for _, p := range h.Def.rules.patterns { + matches := findAllIndex(p.regex, line, start == 0, canMatchEnd) for _, m := range matches { - highlights[start+m[0]] = p.group - if _, ok := highlights[start+m[1]]; !ok { - highlights[start+m[1]] = "" + for i := m[0]; i < m[1]; i++ { + fullHighlights[i] = p.group + } + } + } + for i, h := range fullHighlights { + if i == 0 || h != fullHighlights[i-1] { + if _, ok := highlights[start+i]; !ok { + highlights[start+i] = h } } } @@ -174,85 +256,91 @@ func (h *Highlighter) highlightEmptyRegion(start int, canMatchEnd bool, lineNum return highlights } +// HighlightString syntax highlights a string +// Use this function for simple syntax highlighting and use the other functions for +// more advanced syntax highlighting. They are optimized for quick rehighlighting of the same +// text with minor changes made func (h *Highlighter) HighlightString(input string) []LineMatch { lines := strings.Split(input, "\n") var lineMatches []LineMatch for i := 0; i < len(lines); i++ { - line := []byte(lines[i]) + line := []rune(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)) } } return lineMatches } -func (h *Highlighter) Highlight(input LineStates, startline int) { - lines := input.LineData() - - for i := startline; i < len(lines); i++ { - line := []byte(lines[i]) +// HighlightStates correctly sets all states for the buffer +func (h *Highlighter) HighlightStates(input LineStates) { + for i := 0; i < input.LinesNum(); i++ { + line := []rune(input.Line(i)) + // highlights := make(LineMatch) - var match LineMatch if i == 0 || h.lastRegion == nil { - match = h.highlightEmptyRegion(0, true, i, line) + h.highlightEmptyRegion(nil, 0, true, i, line, true) } else { - match = h.highlightRegion(0, true, i, line, h.lastRegion) + h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true) } curState := h.lastRegion - input.SetMatch(i, match) input.SetState(i, curState) } } -func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) { - lines := input.LineData() +// HighlightMatches sets the matches for each line in between startline and endline +// 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) { + for i := startline; i < endline; i++ { + if i >= input.LinesNum() { + break + } - line := []byte(lines[lineN]) + line := []rune(input.Line(i)) + highlights := make(LineMatch) - h.lastRegion = nil - if lineN > 0 { - h.lastRegion = input.State(lineN - 1) - } + var match LineMatch + if i == 0 || input.State(i-1) == nil { + match = h.highlightEmptyRegion(highlights, 0, true, i, line, false) + } else { + match = h.highlightRegion(highlights, 0, true, i, line, input.State(i-1), false) + } - var match LineMatch - if lineN == 0 || h.lastRegion == nil { - match = h.highlightEmptyRegion(0, true, lineN, line) - } else { - match = h.highlightRegion(0, true, lineN, line, h.lastRegion) + input.SetMatch(i, match) } - curState := h.lastRegion - - input.SetMatch(lineN, match) - input.SetState(lineN, curState) } -func (h *Highlighter) ReHighlight(input LineStates, startline int) { - lines := input.LineData() +// 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() 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 := []rune(input.Line(i)) + // highlights := make(LineMatch) - var match LineMatch + // var match LineMatch if i == 0 || h.lastRegion == nil { - match = h.highlightEmptyRegion(0, true, i, line) + h.highlightEmptyRegion(nil, 0, true, i, line, true) } else { - match = 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) - input.SetMatch(i, match) input.SetState(i, curState) if curState == lastState { @@ -260,3 +348,25 @@ func (h *Highlighter) ReHighlight(input LineStates, startline int) { } } } + +// ReHighlightLine will rehighlight the state and match for a single line +func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) { + line := []rune(input.Line(lineN)) + highlights := make(LineMatch) + + h.lastRegion = nil + if lineN > 0 { + h.lastRegion = input.State(lineN - 1) + } + + var match LineMatch + if lineN == 0 || h.lastRegion == nil { + match = h.highlightEmptyRegion(highlights, 0, true, lineN, line, false) + } else { + match = h.highlightRegion(highlights, 0, true, lineN, line, h.lastRegion, false) + } + curState := h.lastRegion + + input.SetMatch(lineN, match) + input.SetState(lineN, curState) +}