124 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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 file.
 | |
| 
 | |
| //go:build cmp_debug
 | |
| // +build cmp_debug
 | |
| 
 | |
| package diff
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // The algorithm can be seen running in real-time by enabling debugging:
 | |
| //	go test -tags=cmp_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)
 | |
| }
 |