9 func sliceStart(slc []byte, index int) []byte {
15 return slc[totalSize:]
18 _, size := utf8.DecodeRune(slc[totalSize:])
23 return slc[totalSize:]
26 func sliceEnd(slc []byte, index int) []byte {
32 return slc[:totalSize]
35 _, size := utf8.DecodeRune(slc[totalSize:])
40 return slc[:totalSize]
43 // RunePos returns the rune index of a given byte index
44 // This could cause problems if the byte index is between code points
45 func runePos(p int, str []byte) int {
50 return utf8.RuneCount(str)
52 return utf8.RuneCount(str[:p])
55 func combineLineMatch(src, dst LineMatch) LineMatch {
56 for k, v := range src {
57 if g, ok := dst[k]; ok {
68 // A State represents the region at the end of a line
71 // LineStates is an interface for a buffer-like object which can also store the states and matches for every line
72 type LineStates interface {
73 LineBytes(n int) []byte
75 State(lineN int) State
76 SetState(lineN int, s State)
77 SetMatch(lineN int, m LineMatch)
80 // A Highlighter contains the information needed to highlight a string
81 type Highlighter struct {
86 // NewHighlighter returns a new highlighter from the given syntax definition
87 func NewHighlighter(def *Def) *Highlighter {
93 // LineMatch represents the syntax highlighting matches for one line. Each index where the coloring is changed is marked with that
94 // color's group (represented as one byte)
95 type LineMatch map[int]Group
97 func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) []int {
98 regexStr := regex.String()
99 if strings.Contains(regexStr, "^") {
104 if strings.Contains(regexStr, "$") {
112 strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
113 res := make([]byte, utf8.RuneCount(match))
120 match := regex.FindIndex(strbytes)
124 // return []int{match.Index, match.Index + match.Length}
125 return []int{runePos(match[0], str), runePos(match[1], str)}
128 func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int {
129 regexStr := regex.String()
130 if strings.Contains(regexStr, "^") {
135 if strings.Contains(regexStr, "$") {
140 matches := regex.FindAllIndex(str, -1)
141 for i, m := range matches {
142 matches[i][0] = runePos(m[0], str)
143 matches[i][1] = runePos(m[1], str)
148 func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
149 lineLen := utf8.RuneCount(line)
152 if _, ok := highlights[0]; !ok {
153 highlights[0] = curRegion.group
158 loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd)
161 highlights[start+loc[0]] = curRegion.limitGroup
163 if curRegion.parent == nil {
165 highlights[start+loc[1]] = 0
166 h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
168 h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
172 highlights[start+loc[1]] = curRegion.parent.group
173 h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
175 h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
181 h.lastRegion = curRegion
187 firstLoc := []int{lineLen, 0}
189 var firstRegion *region
190 for _, r := range curRegion.rules.regions {
191 loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
193 if loc[0] < firstLoc[0] {
199 if firstLoc[0] != lineLen {
201 highlights[start+firstLoc[0]] = firstRegion.limitGroup
203 h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
204 h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
209 fullHighlights := make([]Group, lineLen)
210 for i := 0; i < len(fullHighlights); i++ {
211 fullHighlights[i] = curRegion.group
214 for _, p := range curRegion.rules.patterns {
215 matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
216 for _, m := range matches {
217 for i := m[0]; i < m[1]; i++ {
218 fullHighlights[i] = p.group
222 for i, h := range fullHighlights {
223 if i == 0 || h != fullHighlights[i-1] {
224 highlights[start+i] = h
230 h.lastRegion = curRegion
236 func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
237 lineLen := utf8.RuneCount(line)
245 firstLoc := []int{lineLen, 0}
246 var firstRegion *region
247 for _, r := range h.Def.rules.regions {
248 loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
250 if loc[0] < firstLoc[0] {
256 if firstLoc[0] != lineLen {
258 highlights[start+firstLoc[0]] = firstRegion.limitGroup
260 h.highlightEmptyRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), statesOnly)
261 h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
273 fullHighlights := make([]Group, len(line))
274 for _, p := range h.Def.rules.patterns {
275 matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
276 for _, m := range matches {
277 for i := m[0]; i < m[1]; i++ {
278 fullHighlights[i] = p.group
282 for i, h := range fullHighlights {
283 if i == 0 || h != fullHighlights[i-1] {
284 // if _, ok := highlights[start+i]; !ok {
285 highlights[start+i] = h
297 // HighlightString syntax highlights a string
298 // Use this function for simple syntax highlighting and use the other functions for
299 // more advanced syntax highlighting. They are optimized for quick rehighlighting of the same
300 // text with minor changes made
301 func (h *Highlighter) HighlightString(input string) []LineMatch {
302 lines := strings.Split(input, "\n")
303 var lineMatches []LineMatch
305 for i := 0; i < len(lines); i++ {
306 line := []byte(lines[i])
307 highlights := make(LineMatch)
309 if i == 0 || h.lastRegion == nil {
310 lineMatches = append(lineMatches, h.highlightEmptyRegion(highlights, 0, true, i, line, false))
312 lineMatches = append(lineMatches, h.highlightRegion(highlights, 0, true, i, line, h.lastRegion, false))
319 // HighlightStates correctly sets all states for the buffer
320 func (h *Highlighter) HighlightStates(input LineStates) {
321 for i := 0; i < input.LinesNum(); i++ {
322 line := input.LineBytes(i)
323 // highlights := make(LineMatch)
325 if i == 0 || h.lastRegion == nil {
326 h.highlightEmptyRegion(nil, 0, true, i, line, true)
328 h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
331 curState := h.lastRegion
333 input.SetState(i, curState)
337 // HighlightMatches sets the matches for each line in between startline and endline
338 // It sets all other matches in the buffer to nil to conserve memory
339 // This assumes that all the states are set correctly
340 func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
341 for i := startline; i < endline; i++ {
342 if i >= input.LinesNum() {
346 line := input.LineBytes(i)
347 highlights := make(LineMatch)
350 if i == 0 || input.State(i-1) == nil {
351 match = h.highlightEmptyRegion(highlights, 0, true, i, line, false)
353 match = h.highlightRegion(highlights, 0, true, i, line, input.State(i-1), false)
356 input.SetMatch(i, match)
360 // ReHighlightStates will scan down from `startline` and set the appropriate end of line state
361 // for each line until it comes across a line whose state does not change
362 // returns the number of the final line
363 func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
364 // lines := input.LineData()
368 h.lastRegion = input.State(startline - 1)
370 for i := startline; i < input.LinesNum(); i++ {
371 line := input.LineBytes(i)
372 // highlights := make(LineMatch)
374 // var match LineMatch
375 if i == 0 || h.lastRegion == nil {
376 h.highlightEmptyRegion(nil, 0, true, i, line, true)
378 h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
380 curState := h.lastRegion
381 lastState := input.State(i)
383 input.SetState(i, curState)
385 if curState == lastState {
390 return input.LinesNum() - 1
393 // ReHighlightLine will rehighlight the state and match for a single line
394 func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
395 line := input.LineBytes(lineN)
396 highlights := make(LineMatch)
400 h.lastRegion = input.State(lineN - 1)
404 if lineN == 0 || h.lastRegion == nil {
405 match = h.highlightEmptyRegion(highlights, 0, true, lineN, line, false)
407 match = h.highlightRegion(highlights, 0, true, lineN, line, h.lastRegion, false)
409 curState := h.lastRegion
411 input.SetMatch(lineN, match)
412 input.SetState(lineN, curState)