123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- // Copyright 2017, The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE.md file.
- // +build debug
- package diff
- import (
- "fmt"
- "strings"
- "sync"
- "time"
- )
- // The algorithm can be seen running in real-time by enabling debugging:
- // go test -tags=debug -v
- //
- // Example output:
- // === RUN TestDifference/#34
- // ┌───────────────────────────────┐
- // │ \ · · · · · · · · · · · · · · │
- // │ · # · · · · · · · · · · · · · │
- // │ · \ · · · · · · · · · · · · · │
- // │ · · \ · · · · · · · · · · · · │
- // │ · · · X # · · · · · · · · · · │
- // │ · · · # \ · · · · · · · · · · │
- // │ · · · · · # # · · · · · · · · │
- // │ · · · · · # \ · · · · · · · · │
- // │ · · · · · · · \ · · · · · · · │
- // │ · · · · · · · · \ · · · · · · │
- // │ · · · · · · · · · \ · · · · · │
- // │ · · · · · · · · · · \ · · # · │
- // │ · · · · · · · · · · · \ # # · │
- // │ · · · · · · · · · · · # # # · │
- // │ · · · · · · · · · · # # # # · │
- // │ · · · · · · · · · # # # # # · │
- // │ · · · · · · · · · · · · · · \ │
- // └───────────────────────────────┘
- // [.Y..M.XY......YXYXY.|]
- //
- // The grid represents the edit-graph where the horizontal axis represents
- // list X and the vertical axis represents list Y. The start of the two lists
- // is the top-left, while the ends are the bottom-right. The '·' represents
- // an unexplored node in the graph. The '\' indicates that the two symbols
- // from list X and Y are equal. The 'X' indicates that two symbols are similar
- // (but not exactly equal) to each other. The '#' indicates that the two symbols
- // are different (and not similar). The algorithm traverses this graph trying to
- // make the paths starting in the top-left and the bottom-right connect.
- //
- // The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
- // the currently established path from the forward and reverse searches,
- // separated by a '|' character.
- const (
- updateDelay = 100 * time.Millisecond
- finishDelay = 500 * time.Millisecond
- ansiTerminal = true // ANSI escape codes used to move terminal cursor
- )
- var debug debugger
- type debugger struct {
- sync.Mutex
- p1, p2 EditScript
- fwdPath, revPath *EditScript
- grid []byte
- lines int
- }
- func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
- dbg.Lock()
- dbg.fwdPath, dbg.revPath = p1, p2
- top := "┌─" + strings.Repeat("──", nx) + "┐\n"
- row := "│ " + strings.Repeat("· ", nx) + "│\n"
- btm := "└─" + strings.Repeat("──", nx) + "┘\n"
- dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
- dbg.lines = strings.Count(dbg.String(), "\n")
- fmt.Print(dbg)
- // Wrap the EqualFunc so that we can intercept each result.
- return func(ix, iy int) (r Result) {
- cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
- for i := range cell {
- cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
- }
- switch r = f(ix, iy); {
- case r.Equal():
- cell[0] = '\\'
- case r.Similar():
- cell[0] = 'X'
- default:
- cell[0] = '#'
- }
- return
- }
- }
- func (dbg *debugger) Update() {
- dbg.print(updateDelay)
- }
- func (dbg *debugger) Finish() {
- dbg.print(finishDelay)
- dbg.Unlock()
- }
- func (dbg *debugger) String() string {
- dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
- for i := len(*dbg.revPath) - 1; i >= 0; i-- {
- dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
- }
- return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
- }
- func (dbg *debugger) print(d time.Duration) {
- if ansiTerminal {
- fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
- }
- fmt.Print(dbg)
- time.Sleep(d)
- }
|