Skip to content

Commit 94710f0

Browse files
committed
highlighter: Rework of matching approach to speed up the processing
1 parent 143f832 commit 94710f0

2 files changed

Lines changed: 153 additions & 111 deletions

File tree

internal/util/util.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ func SliceStartStr(str string, index int) string {
130130
return str[:totalSize]
131131
}
132132

133+
// SliceStartEnd combines SliceStart and SliceEnd into one
134+
func SliceStartEnd(slc []byte, start int, end int) []byte {
135+
return SliceEnd(SliceStart(slc, end), start)
136+
}
137+
138+
// SliceStartEndStr is the same as SliceStartEnd but for strings
139+
func SliceStartEndStr(str string, start int, end int) string {
140+
return SliceEndStr(SliceStartStr(str, end), start)
141+
}
142+
133143
// SliceVisualEnd will take a byte slice and slice off the start
134144
// up to a given visual index. If the index is in the middle of a
135145
// rune the number of visual columns into the rune will be returned

pkg/highlight/highlighter.go

Lines changed: 143 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -71,135 +71,180 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
7171
return []int{util.RunePos(str, match[0]), util.RunePos(str, match[1])}
7272
}
7373

74-
func findAllIndex(regex *regexp.Regexp, str []byte) [][]int {
75-
matches := regex.FindAllIndex(str, -1)
74+
func findAllIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) [][]int {
75+
var strbytes []byte
76+
if skip != nil {
77+
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
78+
res := make([]byte, util.CharacterCount(match))
79+
return res
80+
})
81+
} else {
82+
strbytes = str
83+
}
84+
85+
matches := regex.FindAllIndex(strbytes, -1)
7686
for i, m := range matches {
7787
matches[i][0] = util.RunePos(str, m[0])
7888
matches[i][1] = util.RunePos(str, m[1])
7989
}
8090
return matches
8191
}
8292

83-
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, lineNum int, line []byte, curRegion *region) LineMatch {
84-
lineLen := util.CharacterCount(line)
85-
if start == 0 {
86-
if _, ok := highlights[0]; !ok {
87-
highlights[0] = curRegion.group
93+
func (h *Highlighter) highlightRange(fullHighlights []Group, start int, end int, group Group) {
94+
if start <= end && end <= len(fullHighlights) {
95+
for i := start; i < end; i++ {
96+
fullHighlights[i] = group
8897
}
8998
}
99+
}
90100

91-
var nestedRegion *region
92-
nestedLoc := []int{lineLen, 0}
93-
searchNesting := true
94-
endLoc := findIndex(curRegion.end, curRegion.skip, line)
95-
if endLoc != nil {
96-
if start == endLoc[0] {
97-
searchNesting = false
98-
} else {
99-
nestedLoc = endLoc
100-
}
101+
func (h *Highlighter) highlightPatterns(fullHighlights []Group, start int, lineNum int, line []byte, curRegion *region) {
102+
lineLen := util.CharacterCount(line)
103+
if lineLen == 0 {
104+
return
101105
}
102-
if searchNesting {
103-
for _, r := range curRegion.rules.regions {
104-
loc := findIndex(r.start, r.skip, line)
105-
if loc != nil {
106-
if loc[0] < nestedLoc[0] {
107-
nestedLoc = loc
108-
nestedRegion = r
109-
}
106+
107+
var patterns []*pattern
108+
if curRegion == nil {
109+
patterns = h.Def.rules.patterns
110+
} else {
111+
patterns = curRegion.rules.patterns
112+
}
113+
114+
for _, p := range patterns {
115+
if curRegion == nil || curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
116+
matches := findAllIndex(p.regex, nil, line)
117+
for _, m := range matches {
118+
h.highlightRange(fullHighlights, start+m[0], start+m[1], p.group)
110119
}
111120
}
112121
}
113-
if nestedRegion != nil && nestedLoc[0] != lineLen {
114-
highlights[start+nestedLoc[0]] = nestedRegion.limitGroup
115-
slice := util.SliceEnd(line, nestedLoc[1])
116-
h.highlightEmptyRegion(highlights, start+nestedLoc[1], lineNum, slice)
117-
h.highlightRegion(highlights, start+nestedLoc[1], lineNum, slice, nestedRegion)
118-
return highlights
119-
}
122+
}
120123

121-
fullHighlights := make([]Group, lineLen)
122-
for i := 0; i < len(fullHighlights); i++ {
123-
fullHighlights[i] = curRegion.group
124+
func (h *Highlighter) highlightRegions(fullHighlights []Group, start int, lineNum int, line []byte, curRegion *region, regions []*region, nestedRegion bool) {
125+
lineLen := util.CharacterCount(line)
126+
if lineLen == 0 {
127+
return
124128
}
125129

126-
if searchNesting {
127-
for _, p := range curRegion.rules.patterns {
128-
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
129-
matches := findAllIndex(p.regex, line)
130-
for _, m := range matches {
131-
if endLoc == nil || m[0] < endLoc[0] {
132-
for i := m[0]; i < m[1]; i++ {
133-
fullHighlights[i] = p.group
130+
h.highlightPatterns(fullHighlights, start, lineNum, line, curRegion)
131+
132+
lastStart := 0
133+
regionStart := 0
134+
regionEnd := 0
135+
for _, r := range regions {
136+
if !nestedRegion && curRegion != nil && (curRegion.group != r.group || curRegion.start != r.start) {
137+
continue
138+
}
139+
startMatches := findAllIndex(r.start, r.skip, line)
140+
endMatches := findAllIndex(r.end, r.skip, line)
141+
samePattern := false
142+
startLoop:
143+
for startIdx := 0; startIdx < len(startMatches); startIdx++ {
144+
if startMatches[startIdx][0] < lineLen {
145+
for endIdx := 0; endIdx < len(endMatches); endIdx++ {
146+
if startMatches[startIdx][0] == endMatches[endIdx][0] {
147+
// start and end are the same (pattern)
148+
samePattern = true
149+
if len(startMatches) == len(endMatches) {
150+
// special case in the moment both are the same
151+
if curRegion != nil && curRegion.group == r.group && curRegion.start == r.start {
152+
if len(startMatches) > 1 {
153+
// end < start
154+
continue startLoop
155+
} else if len(startMatches) > 0 {
156+
// ... end
157+
startIdx = len(startMatches)
158+
continue startLoop
159+
}
160+
} else {
161+
// start ... or start < end
162+
}
163+
}
164+
} else if startMatches[startIdx][1] <= endMatches[endIdx][0] {
165+
if regionStart < startMatches[startIdx][0] && startMatches[startIdx][0] < regionEnd {
166+
continue
167+
}
168+
// start and end at the current line
169+
regionStart = startMatches[startIdx][0]
170+
regionEnd = endMatches[endIdx][1]
171+
h.highlightRange(fullHighlights, start+startMatches[startIdx][0], start+endMatches[endIdx][1], r.limitGroup)
172+
h.highlightRegions(fullHighlights, start+startMatches[startIdx][1], lineNum, util.SliceStartEnd(line, startMatches[startIdx][1], endMatches[endIdx][0]), r, r.rules.regions, true)
173+
if samePattern {
174+
startIdx += 1
175+
}
176+
if lastStart == 0 || lastStart < endMatches[endIdx][1] {
177+
if curRegion != nil {
178+
h.lastRegion = curRegion.parent
179+
} else {
180+
h.lastRegion = nil
181+
}
182+
curRegion = h.lastRegion
183+
}
184+
continue startLoop
185+
} else if endMatches[endIdx][1] <= startMatches[startIdx][0] {
186+
if endMatches[endIdx][0] < regionEnd || curRegion == nil {
187+
continue
134188
}
189+
// start and end at the current line, but switched
190+
h.highlightRange(fullHighlights, start, start+endMatches[endIdx][1], r.limitGroup)
191+
h.highlightRegions(fullHighlights, start, lineNum, util.SliceStart(line, endMatches[endIdx][0]), r, r.rules.regions, true)
192+
h.highlightPatterns(fullHighlights, start+endMatches[endIdx][1], lineNum, util.SliceStartEnd(line, endMatches[endIdx][1], startMatches[startIdx][0]), nil)
193+
if curRegion != nil {
194+
h.lastRegion = curRegion.parent
195+
} else {
196+
h.lastRegion = nil
197+
}
198+
curRegion = h.lastRegion
199+
}
200+
}
201+
if regionEnd <= startMatches[startIdx][0] {
202+
// start at the current, but end at the next line
203+
regionStart = startMatches[startIdx][0]
204+
regionEnd = 0
205+
h.highlightRange(fullHighlights, start+startMatches[startIdx][0], lineLen, r.limitGroup)
206+
h.highlightRegions(fullHighlights, start+startMatches[startIdx][1], lineNum, util.SliceEnd(line, startMatches[startIdx][1]), r, r.rules.regions, true)
207+
if lastStart <= startMatches[startIdx][0] {
208+
lastStart = startMatches[startIdx][0]
209+
h.lastRegion = r
135210
}
136211
}
137212
}
138213
}
139-
}
140-
for i, h := range fullHighlights {
141-
if i == 0 || h != fullHighlights[i-1] {
142-
highlights[start+i] = h
143-
}
144-
}
145-
146-
loc := endLoc
147-
if loc != nil {
148-
highlights[start+loc[0]] = curRegion.limitGroup
149-
if curRegion.parent == nil {
150-
highlights[start+loc[1]] = 0
151-
h.highlightEmptyRegion(highlights, start+loc[1], lineNum, util.SliceEnd(line, loc[1]))
152-
return highlights
214+
if curRegion != nil && curRegion.group == r.group && curRegion.start == r.start {
215+
if (len(startMatches) == 0 && len(endMatches) > 0) || (samePattern && (len(startMatches) == len(endMatches))) {
216+
for _, endMatch := range endMatches {
217+
// end at the current, but start at the previous line
218+
h.highlightRange(fullHighlights, start, start+endMatch[1], r.limitGroup)
219+
h.highlightRegions(fullHighlights, start, lineNum, util.SliceStart(line, endMatch[0]), r, r.rules.regions, true)
220+
if curRegion != nil {
221+
h.lastRegion = curRegion.parent
222+
} else {
223+
h.lastRegion = nil
224+
}
225+
curRegion = h.lastRegion
226+
break
227+
}
228+
} else if len(startMatches) == 0 && len(endMatches) == 0 {
229+
// no start and end found in this region
230+
h.highlightRange(fullHighlights, start, lineLen, curRegion.group)
231+
}
153232
}
154-
highlights[start+loc[1]] = curRegion.parent.group
155-
h.highlightRegion(highlights, start+loc[1], lineNum, util.SliceEnd(line, loc[1]), curRegion.parent)
156-
return highlights
157233
}
158-
159-
h.lastRegion = curRegion
160-
161-
return highlights
162234
}
163235

164-
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, lineNum int, line []byte) LineMatch {
236+
func (h *Highlighter) highlight(highlights LineMatch, start int, lineNum int, line []byte, curRegion *region) LineMatch {
165237
lineLen := util.CharacterCount(line)
166238
if lineLen == 0 {
167-
h.lastRegion = nil
168239
return highlights
169240
}
170241

171-
var firstRegion *region
172-
firstLoc := []int{lineLen, 0}
173-
for _, r := range h.Def.rules.regions {
174-
loc := findIndex(r.start, r.skip, line)
175-
if loc != nil {
176-
if loc[0] < firstLoc[0] {
177-
firstLoc = loc
178-
firstRegion = r
179-
}
180-
}
181-
}
182-
if firstRegion != nil && firstLoc[0] != lineLen {
183-
highlights[start+firstLoc[0]] = firstRegion.limitGroup
184-
h.highlightEmptyRegion(highlights, start, lineNum, util.SliceStart(line, firstLoc[0]))
185-
h.highlightRegion(highlights, start+firstLoc[1], lineNum, util.SliceEnd(line, firstLoc[1]), firstRegion)
186-
return highlights
187-
}
242+
fullHighlights := make([]Group, lineLen)
243+
h.highlightRegions(fullHighlights, start, lineNum, line, curRegion, h.Def.rules.regions, false)
188244

189-
fullHighlights := make([]Group, len(line))
190-
for _, p := range h.Def.rules.patterns {
191-
matches := findAllIndex(p.regex, line)
192-
for _, m := range matches {
193-
for i := m[0]; i < m[1]; i++ {
194-
fullHighlights[i] = p.group
195-
}
196-
}
197-
}
198245
for i, h := range fullHighlights {
199246
if i == 0 || h != fullHighlights[i-1] {
200-
// if _, ok := highlights[start+i]; !ok {
201-
highlights[start+i] = h
202-
// }
247+
highlights[i] = h
203248
}
204249
}
205250

@@ -217,12 +262,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
217262
for i := 0; i < len(lines); i++ {
218263
line := []byte(lines[i])
219264
highlights := make(LineMatch)
220-
221-
if i == 0 || h.lastRegion == nil {
222-
lineMatches = append(lineMatches, h.highlightEmptyRegion(highlights, 0, i, line))
223-
} else {
224-
lineMatches = append(lineMatches, h.highlightRegion(highlights, 0, i, line, h.lastRegion))
225-
}
265+
lineMatches = append(lineMatches, h.highlight(highlights, 0, i, line, nil))
226266
}
227267

228268
return lineMatches
@@ -245,11 +285,7 @@ func (h *Highlighter) Highlight(input LineStates, startline, endline int) {
245285
highlights := make(LineMatch)
246286

247287
var match LineMatch
248-
if i == 0 || h.lastRegion == nil {
249-
match = h.highlightEmptyRegion(highlights, 0, i, line)
250-
} else {
251-
match = h.highlightRegion(highlights, 0, i, line, h.lastRegion)
252-
}
288+
match = h.highlight(highlights, 0, i, line, h.lastRegion)
253289

254290
input.SetState(i, h.lastRegion)
255291
input.SetMatch(i, match)
@@ -267,11 +303,7 @@ func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
267303
}
268304

269305
var match LineMatch
270-
if lineN == 0 || h.lastRegion == nil {
271-
match = h.highlightEmptyRegion(highlights, 0, lineN, line)
272-
} else {
273-
match = h.highlightRegion(highlights, 0, lineN, line, h.lastRegion)
274-
}
306+
match = h.highlight(highlights, 0, lineN, line, h.lastRegion)
275307

276308
input.SetState(lineN, h.lastRegion)
277309
input.SetMatch(lineN, match)

0 commit comments

Comments
 (0)