1// Copyright 2017, The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build cmp_debug
6// +build cmp_debug
7
8package diff
9
10import (
11 "fmt"
12 "strings"
13 "sync"
14 "time"
15)
16
17// The algorithm can be seen running in real-time by enabling debugging:
18// go test -tags=cmp_debug -v
19//
20// Example output:
21// === RUN TestDifference/#34
22// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
23// โ \ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท โ
24// โ ยท # ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท โ
25// โ ยท \ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท โ
26// โ ยท ยท \ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท โ
27// โ ยท ยท ยท X # ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท โ
28// โ ยท ยท ยท # \ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท โ
29// โ ยท ยท ยท ยท ยท # # ยท ยท ยท ยท ยท ยท ยท ยท โ
30// โ ยท ยท ยท ยท ยท # \ ยท ยท ยท ยท ยท ยท ยท ยท โ
31// โ ยท ยท ยท ยท ยท ยท ยท \ ยท ยท ยท ยท ยท ยท ยท โ
32// โ ยท ยท ยท ยท ยท ยท ยท ยท \ ยท ยท ยท ยท ยท ยท โ
33// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท \ ยท ยท ยท ยท ยท โ
34// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท \ ยท ยท # ยท โ
35// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท \ # # ยท โ
36// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท # # # ยท โ
37// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท # # # # ยท โ
38// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท # # # # # ยท โ
39// โ ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท ยท \ โ
40// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
41// [.Y..M.XY......YXYXY.|]
42//
43// The grid represents the edit-graph where the horizontal axis represents
44// list X and the vertical axis represents list Y. The start of the two lists
45// is the top-left, while the ends are the bottom-right. The 'ยท' represents
46// an unexplored node in the graph. The '\' indicates that the two symbols
47// from list X and Y are equal. The 'X' indicates that two symbols are similar
48// (but not exactly equal) to each other. The '#' indicates that the two symbols
49// are different (and not similar). The algorithm traverses this graph trying to
50// make the paths starting in the top-left and the bottom-right connect.
51//
52// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
53// the currently established path from the forward and reverse searches,
54// separated by a '|' character.
55
56const (
57 updateDelay = 100 * time.Millisecond
58 finishDelay = 500 * time.Millisecond
59 ansiTerminal = true // ANSI escape codes used to move terminal cursor
60)
61
62var debug debugger
63
64type debugger struct {
65 sync.Mutex
66 p1, p2 EditScript
67 fwdPath, revPath *EditScript
68 grid []byte
69 lines int
70}
71
72func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
73 dbg.Lock()
74 dbg.fwdPath, dbg.revPath = p1, p2
75 top := "โโ" + strings.Repeat("โโ", nx) + "โ\n"
76 row := "โ " + strings.Repeat("ยท ", nx) + "โ\n"
77 btm := "โโ" + strings.Repeat("โโ", nx) + "โ\n"
78 dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
79 dbg.lines = strings.Count(dbg.String(), "\n")
80 fmt.Print(dbg)
81
82 // Wrap the EqualFunc so that we can intercept each result.
83 return func(ix, iy int) (r Result) {
84 cell := dbg.grid[len(top)+iy*len(row):][len("โ ")+len("ยท ")*ix:][:len("ยท")]
85 for i := range cell {
86 cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
87 }
88 switch r = f(ix, iy); {
89 case r.Equal():
90 cell[0] = '\\'
91 case r.Similar():
92 cell[0] = 'X'
93 default:
94 cell[0] = '#'
95 }
96 return
97 }
98}
99
100func (dbg *debugger) Update() {
101 dbg.print(updateDelay)
102}
103
104func (dbg *debugger) Finish() {
105 dbg.print(finishDelay)
106 dbg.Unlock()
107}
108
109func (dbg *debugger) String() string {
110 dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
111 for i := len(*dbg.revPath) - 1; i >= 0; i-- {
112 dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
113 }
114 return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
115}
116
117func (dbg *debugger) print(d time.Duration) {
118 if ansiTerminal {
119 fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
120 }
121 fmt.Print(dbg)
122 time.Sleep(d)
123}