8 func sliceStart(slc []byte, index int) []byte {
14 return slc[totalSize:]
17 _, _, size := DecodeCharacter(slc[totalSize:])
22 return slc[totalSize:]
25 func sliceEnd(slc []byte, index int) []byte {
31 return slc[:totalSize]
34 _, _, size := DecodeCharacter(slc[totalSize:])
39 return slc[:totalSize]
42 // RunePos returns the rune index of a given byte index
43 // This could cause problems if the byte index is between code points
44 func runePos(p int, str []byte) int {
49 return CharacterCount(str)
51 return CharacterCount(str[:p])
54 func combineLineMatch(src, dst LineMatch) LineMatch {
55 for k, v := range src {
56 if g, ok := dst[k]; ok {
67 // A State represents the region at the end of a line
70 // EmptyDef is an empty definition.
71 var EmptyDef = Def{nil, &rules{}}
73 // LineStates is an interface for a buffer-like object which can also store the states and matches for every line
74 type LineStates interface {
75 LineBytes(n int) []byte
77 State(lineN int) State
78 SetState(lineN int, s State)
79 SetMatch(lineN int, m LineMatch)
82 // A Highlighter contains the information needed to highlight a string
83 type Highlighter struct {
88 // NewHighlighter returns a new highlighter from the given syntax definition
89 func NewHighlighter(def *Def) *Highlighter {
95 // LineMatch represents the syntax highlighting matches for one line. Each index where the coloring is changed is marked with that
96 // color's group (represented as one byte)
97 type LineMatch map[int]Group
99 func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) []int {
100 regexStr := regex.String()
101 if strings.Contains(regexStr, "^") {
106 if strings.Contains(regexStr, "$") {
114 strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
115 res := make([]byte, CharacterCount(match))
122 match := regex.FindIndex(strbytes)
126 // return []int{match.Index, match.Index + match.Length}
127 return []int{runePos(match[0], str), runePos(match[1], str)}
130 func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int {
131 regexStr := regex.String()
132 if strings.Contains(regexStr, "^") {
137 if strings.Contains(regexStr, "$") {
142 matches := regex.FindAllIndex(str, -1)
143 for i, m := range matches {
144 matches[i][0] = runePos(m[0], str)
145 matches[i][1] = runePos(m[1], str)
150 func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
151 lineLen := CharacterCount(line)
154 if _, ok := highlights[0]; !ok {
155 highlights[0] = curRegion.group
160 loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd)
163 highlights[start+loc[0]] = curRegion.limitGroup
165 if curRegion.parent == nil {
167 highlights[start+loc[1]] = 0
168 h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
170 h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
174 highlights[start+loc[1]] = curRegion.parent.group
175 h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
177 h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
183 h.lastRegion = curRegion
189 firstLoc := []int{lineLen, 0}
191 var firstRegion *region
192 for _, r := range curRegion.rules.regions {
193 loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
195 if loc[0] < firstLoc[0] {
201 if firstLoc[0] != lineLen {
203 highlights[start+firstLoc[0]] = firstRegion.limitGroup
205 h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
206 h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
211 fullHighlights := make([]Group, lineLen)
212 for i := 0; i < len(fullHighlights); i++ {
213 fullHighlights[i] = curRegion.group
216 for _, p := range curRegion.rules.patterns {
217 matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
218 for _, m := range matches {
219 for i := m[0]; i < m[1]; i++ {
220 fullHighlights[i] = p.group
224 for i, h := range fullHighlights {
225 if i == 0 || h != fullHighlights[i-1] {
226 highlights[start+i] = h
232 h.lastRegion = curRegion
238 func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
239 lineLen := CharacterCount(line)
247 firstLoc := []int{lineLen, 0}
248 var firstRegion *region
249 for _, r := range h.Def.rules.regions {
250 loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
252 if loc[0] < firstLoc[0] {
258 if firstLoc[0] != lineLen {
260 highlights[start+firstLoc[0]] = firstRegion.limitGroup
262 h.highlightEmptyRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), statesOnly)
263 h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
275 fullHighlights := make([]Group, len(line))
276 for _, p := range h.Def.rules.patterns {
277 matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
278 for _, m := range matches {
279 for i := m[0]; i < m[1]; i++ {
280 fullHighlights[i] = p.group
284 for i, h := range fullHighlights {
285 if i == 0 || h != fullHighlights[i-1] {
286 // if _, ok := highlights[start+i]; !ok {
287 highlights[start+i] = h
299 // HighlightString syntax highlights a string
300 // Use this function for simple syntax highlighting and use the other functions for
301 // more advanced syntax highlighting. They are optimized for quick rehighlighting of the same
302 // text with minor changes made
303 func (h *Highlighter) HighlightString(input string) []LineMatch {
304 lines := strings.Split(input, "\n")
305 var lineMatches []LineMatch
307 for i := 0; i < len(lines); i++ {
308 line := []byte(lines[i])
309 highlights := make(LineMatch)
311 if i == 0 || h.lastRegion == nil {
312 lineMatches = append(lineMatches, h.highlightEmptyRegion(highlights, 0, true, i, line, false))
314 lineMatches = append(lineMatches, h.highlightRegion(highlights, 0, true, i, line, h.lastRegion, false))
321 // HighlightStates correctly sets all states for the buffer
322 func (h *Highlighter) HighlightStates(input LineStates) {
323 for i := 0; i < input.LinesNum(); i++ {
324 line := input.LineBytes(i)
325 // highlights := make(LineMatch)
327 if i == 0 || h.lastRegion == nil {
328 h.highlightEmptyRegion(nil, 0, true, i, line, true)
330 h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
333 curState := h.lastRegion
335 input.SetState(i, curState)
339 // HighlightMatches sets the matches for each line from startline to endline
340 // It sets all other matches in the buffer to nil to conserve memory
341 // This assumes that all the states are set correctly
342 func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
343 for i := startline; i <= endline; i++ {
344 if i >= input.LinesNum() {
348 line := input.LineBytes(i)
349 highlights := make(LineMatch)
352 if i == 0 || input.State(i-1) == nil {
353 match = h.highlightEmptyRegion(highlights, 0, true, i, line, false)
355 match = h.highlightRegion(highlights, 0, true, i, line, input.State(i-1), false)
358 input.SetMatch(i, match)
362 // ReHighlightStates will scan down from `startline` and set the appropriate end of line state
363 // for each line until it comes across a line whose state does not change
364 // returns the number of the final line
365 func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
366 // lines := input.LineData()
370 h.lastRegion = input.State(startline - 1)
372 for i := startline; i < input.LinesNum(); i++ {
373 line := input.LineBytes(i)
374 // highlights := make(LineMatch)
376 // var match LineMatch
377 if i == 0 || h.lastRegion == nil {
378 h.highlightEmptyRegion(nil, 0, true, i, line, true)
380 h.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)
382 curState := h.lastRegion
383 lastState := input.State(i)
385 input.SetState(i, curState)
387 if curState == lastState {
392 return input.LinesNum() - 1
395 // ReHighlightLine will rehighlight the state and match for a single line
396 func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
397 line := input.LineBytes(lineN)
398 highlights := make(LineMatch)
402 h.lastRegion = input.State(lineN - 1)
406 if lineN == 0 || h.lastRegion == nil {
407 match = h.highlightEmptyRegion(highlights, 0, true, lineN, line, false)
409 match = h.highlightRegion(highlights, 0, true, lineN, line, h.lastRegion, false)
411 curState := h.lastRegion
413 input.SetMatch(lineN, match)
414 input.SetState(lineN, curState)