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