// ----------------------------------------------------------------------------
// Implementation of FormatSelections
-// A Selection is a function returning offset pairs []int{a, b}
-// describing consecutive non-overlapping text segments [a, b).
-// If there are no more segments, a Selection must return nil.
+// A Segment describes a text segment [start, end).
+// The zero value of a Segment is a ready-to-use empty segment.
//
-// TODO It's more efficient to return a pair (a, b int) instead
-// of creating lots of slices. Need to determine how to
-// indicate the end of a Selection.
+type Segment struct {
+ start, end int
+}
+
+func (seg *Segment) isEmpty() bool { return seg.start >= seg.end }
+
+// A Selection is an "iterator" function returning a text segment.
+// Repeated calls to a selection return consecutive, non-overlapping,
+// non-empty segments, followed by an infinite sequence of empty
+// segments. The first empty segment marks the end of the selection.
//
-type Selection func() []int
+type Selection func() Segment
// A LinkWriter writes some start or end "tag" to w for the text offset offs.
// It is called by FormatSelections at the start or end of each link segment.
//
type merger struct {
selections []Selection
- segments [][]int // segments[i] is the next segment of selections[i]
+ segments []Segment // segments[i] is the next segment of selections[i]
}
const infinity int = 2e9
func newMerger(selections []Selection) *merger {
- segments := make([][]int, len(selections))
+ segments := make([]Segment, len(selections))
for i, sel := range selections {
- segments[i] = []int{infinity, infinity}
+ segments[i] = Segment{infinity, infinity}
if sel != nil {
- if seg := sel(); seg != nil {
+ if seg := sel(); !seg.isEmpty() {
segments[i] = seg
}
}
index = -1
for i, seg := range m.segments {
switch {
- case seg[0] < offs:
- offs = seg[0]
+ case seg.start < offs:
+ offs = seg.start
index = i
start = true
- case seg[1] < offs:
- offs = seg[1]
+ case seg.end < offs:
+ offs = seg.end
index = i
start = false
}
// either way it is ok to consume the start offset: set it
// to infinity so it won't be considered in the following
// next call
- m.segments[index][0] = infinity
+ m.segments[index].start = infinity
if start {
return
}
// end offset found - consume it
- m.segments[index][1] = infinity
+ m.segments[index].end = infinity
// advance to the next segment for that selection
seg := m.selections[index]()
- if seg == nil {
- return
+ if !seg.isEmpty() {
+ m.segments[index] = seg
}
- m.segments[index] = seg
return
}
// lineSelection returns the line segments for text as a Selection.
func lineSelection(text []byte) Selection {
i, j := 0, 0
- return func() (seg []int) {
+ return func() (seg Segment) {
// find next newline, if any
for j < len(text) {
j++
}
if i < j {
// text[i:j] constitutes a line
- seg = []int{i, j}
+ seg = Segment{i, j}
i = j
}
return
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
s.Init(file, src, nil, scanner.ScanComments)
- return func() (seg []int) {
+ return func() (seg Segment) {
for {
pos, tok, lit := s.Scan()
if tok == token.EOF {
}
offs := file.Offset(pos)
if tok == sel {
- seg = []int{offs, offs + len(lit)}
+ seg = Segment{offs, offs + len(lit)}
break
}
}
}
// makeSelection is a helper function to make a Selection from a slice of pairs.
+// Pairs describing empty segments are ignored.
+//
func makeSelection(matches [][]int) Selection {
- return func() (seg []int) {
- if len(matches) > 0 {
- seg = matches[0]
- matches = matches[1:]
+ i := 0
+ return func() Segment {
+ for i < len(matches) {
+ m := matches[i]
+ i++
+ if m[0] < m[1] {
+ // non-empty segment
+ return Segment{m[0], m[1]}
+ }
}
- return
+ return Segment{}
}
}