// lineDiff returns b with all lines added or changed from a highlighted. // It discards spaces within lines when comparing lines, so subtle // gofmt-induced alignment changes are not flagged as changes. // It also handles single-line diffs specially, highlighting only the // changes within those lines. func lineDiff(a, b []byte) []byte { l := byteLines{bytes.Split(a, []byte("\n")), bytes.Split(b, []byte("\n"))} cs := diff.Diff(len(l.a), len(l.b), diff.Data(l)) var buf bytes.Buffer n := 0 for _, c := range cs { for _, b := range l.b[n:c.B] { buf.Write(b) buf.WriteByte('\n') } if c.Ins > 0 { if c.Ins == 1 && c.Del == 1 { buf.Write(byteDiff(l.a[c.A], l.b[c.B])) buf.WriteByte('\n') } else { for _, b := range l.b[c.B : c.B+c.Ins] { buf.Write(colorize(b)) buf.WriteByte('\n') } } } n = c.B + c.Ins } for i, b := range l.b[n:] { if i > 0 { buf.WriteByte('\n') } buf.Write(b) } return buf.Bytes() }
func (this *tokenizedDiff) Update() { left := this.GetSources()[0].(*tokenizedGoContent) right := this.GetSources()[1].(*tokenizedGoContent) this.segments = nil dmp := tokenizedDiffHelper{left: left, right: right} diffs := diff.Diff(left.LenSegments(), right.LenSegments(), &dmp) // HACK: Fake first element. this.segments = append(this.segments, highlightSegment{offset: 0}) for _, diff := range diffs { if !this.leftSide && diff.Ins > 0 { beginOffset := right.Segment(uint32(diff.B)).offset endOffset := right.Segment(uint32(diff.B + diff.Ins)).offset this.segments = append(this.segments, highlightSegment{offset: beginOffset, color: darkGreenColor}) this.segments = append(this.segments, highlightSegment{offset: endOffset}) } else if this.leftSide && diff.Del > 0 { beginOffset := left.Segment(uint32(diff.A)).offset endOffset := left.Segment(uint32(diff.A + diff.Del)).offset this.segments = append(this.segments, highlightSegment{offset: beginOffset, color: darkRedColor}) this.segments = append(this.segments, highlightSegment{offset: endOffset}) } } // HACK: Fake last element. this.segments = append(this.segments, highlightSegment{offset: uint32(500000000)}) // TODO, HACK }
func BenchmarkDiff(b *testing.B) { t := tests[len(tests)-1] d := &ints{t.a, t.b} n, m := len(d.a), len(d.b) for i := 0; i < b.N; i++ { diff.Diff(n, m, d) } }
func ExampleInterface() { m := &MixedInput{ []int{1, 2, 3, 1, 2, 2, 1}, []string{"three", "two", "one", "two", "one", "three"}, } changes := diff.Diff(len(m.A), len(m.B), m) for _, c := range changes { fmt.Println("change at", c.A, c.B) } }
func ExampleDiff() { names = map[string]int{ "one": 1, "two": 2, "three": 3, } m := &MixedInput{ []int{1, 2, 3, 1, 2, 2, 1}, []string{"three", "two", "one", "two", "one", "three"}, } changes := diff.Diff(len(m.A), len(m.B), m) for _, c := range changes { fmt.Println("change at", c.A, c.B) } // Output: // change at 0 0 // change at 2 2 // change at 5 4 // change at 7 5 }
// HTMLdiff finds all the differences in the versions of HTML snippits, // versions[0] is the original, all other versions are the edits to be compared. // The resulting merged HTML snippits are as many as there are edits to compare. func (c *Config) HTMLdiff(versions []string) ([]string, error) { if len(versions) < 2 { return nil, errors.New("there must be at least two versions to diff, the 0th element is the base") } parallelErrors := make(chan error, len(versions)) sourceTrees := make([]*html.Node, len(versions)) sourceTreeRunes := make([]*[]treeRune, len(versions)) firstLeaves := make([]int, len(versions)) for v, vv := range versions { go func(v int, vv string) { var err error sourceTrees[v], err = html.Parse(strings.NewReader(vv)) if err == nil { tr := make([]treeRune, 0, c.clean(sourceTrees[v])) sourceTreeRunes[v] = &tr renderTreeRunes(sourceTrees[v], &tr) leaf1, ok := firstLeaf(findBody(sourceTrees[v])) if leaf1 == nil || !ok { firstLeaves[v] = 0 // could be wrong, but correct for simple examples } else { for x, y := range tr { if y.leaf == leaf1 { firstLeaves[v] = x break } } } } parallelErrors <- err }(v, vv) } for range versions { if err := <-parallelErrors; err != nil { return nil, err } } // now all the input trees are buit, we can do the merge mergedHTMLs := make([]string, len(versions)-1) for m := range mergedHTMLs { go func(m int) { treeRuneLimit := 250000 // from initial testing if len(*sourceTreeRunes[0]) > treeRuneLimit || len(*sourceTreeRunes[m+1]) > treeRuneLimit { parallelErrors <- errors.New("input data too large") return } dd := diffData{a: sourceTreeRunes[0], b: sourceTreeRunes[m+1]} var changes []diff.Change ch := make(chan []diff.Change) go func(ch chan []diff.Change) { ch <- diff.Diff(len(*sourceTreeRunes[0]), len(*sourceTreeRunes[m+1]), dd) }(ch) to := time.After(time.Second * 3) select { case <-to: parallelErrors <- errors.New("diff.Diff() took too long") go func(ch chan []diff.Change) { <-ch // make sure the timed-out diff cleans-up }(ch) return case changes = <-ch: // we have the diff go func(to <-chan time.Time) { <-to // make sure we don't leak the timer goroutine }(to) } changes = granular(c.Granularity, dd, changes) mergedTree, err := c.walkChanges(changes, sourceTreeRunes[0], sourceTreeRunes[m+1], firstLeaves[0], firstLeaves[m+1]) if err != nil { parallelErrors <- err return } var mergedHTMLbuff bytes.Buffer err = html.Render(&mergedHTMLbuff, mergedTree) if err != nil { parallelErrors <- err return } mergedHTML := mergedHTMLbuff.Bytes() pfx := []byte("<html><head></head><body>") sfx := []byte("</body></html>") if bytes.HasPrefix(mergedHTML, pfx) && bytes.HasSuffix(mergedHTML, sfx) { mergedHTML = bytes.TrimSuffix(bytes.TrimPrefix(mergedHTML, pfx), sfx) mergedHTMLs[m] = string(mergedHTML) parallelErrors <- nil return } parallelErrors <- errors.New("correct render wrapper HTML not found: " + string(mergedHTML)) }(m) } for range mergedHTMLs { if err := <-parallelErrors; err != nil { return nil, err } } return mergedHTMLs, nil }
func (d *lineDiffer) Diff() []mb0diff.Change { return mb0diff.Diff(len(d.a), len(d.b), d) }
func (this *lineDiff) Update() { left := this.GetSources()[0].(caret.MultilineContentI) right := this.GetSources()[1].(caret.MultilineContentI) dmp := lineDiffHelper{left: left, right: right} diffs := diff.Diff(left.LenLines(), right.LenLines(), &dmp) for side := range this.segments { this.segments[side] = nil // HACK: Fake first element. this.segments[side] = append(this.segments[side], highlightSegment{offset: 0}) } this.lines[0] = make([]bool, left.LenLines()) this.lines[1] = make([]bool, right.LenLines()) for _, diff := range diffs { if diff.Del > 0 || diff.Ins > 0 { beginOffsetLeft := left.Line(diff.A).Start() endOffsetLeft := left.Line(diff.A + diff.Del).Start() beginOffsetRight := right.Line(diff.B).Start() endOffsetRight := right.Line(diff.B + diff.Ins).Start() for line := diff.A; line < diff.A+diff.Del; line++ { this.lines[0][line] = true } for line := diff.B; line < diff.B+diff.Ins; line++ { this.lines[1][line] = true } leftContent := left.Content()[beginOffsetLeft:endOffsetLeft] rightContent := right.Content()[beginOffsetRight:endOffsetRight] highlightedDiffFunc(leftContent, rightContent, &this.segments, [2]uint32{beginOffsetLeft, beginOffsetRight}) this.segments[0] = append(this.segments[0], highlightSegment{offset: endOffsetLeft}) this.segments[1] = append(this.segments[1], highlightSegment{offset: endOffsetRight}) } /* else { for side := range this.segments { if side == 0 && diff.Del > 0 { beginOffset := left.Line(diff.A).Start() endOffset := left.Line(diff.A + diff.Del).Start() this.segments[side] = append(this.segments[side], highlightSegment{offset: beginOffset, color: darkRedColor}) this.segments[side] = append(this.segments[side], highlightSegment{offset: endOffset}) } if side == 1 && diff.Ins > 0 { beginOffset := right.Line(diff.B).Start() endOffset := right.Line(diff.B + diff.Ins).Start() this.segments[side] = append(this.segments[side], highlightSegment{offset: beginOffset, color: darkGreenColor}) this.segments[side] = append(this.segments[side], highlightSegment{offset: endOffset}) } } }*/ } for side := range this.segments { // HACK: Fake last element. this.segments[side] = append(this.segments[side], highlightSegment{offset: uint32(500000000)}) // TODO, HACK } }