1
1
package dotty .tools .dotc .util
2
2
3
3
import scala .annotation .tailrec
4
- import difflib ._
4
+
5
+ import scala .collection .mutable
5
6
6
7
object DiffUtil {
7
8
@@ -13,9 +14,8 @@ object DiffUtil {
13
14
private final val ADDITION_COLOR = ANSI_GREEN
14
15
15
16
def mkColoredCodeDiff (code : String , lastCode : String , printDiffDel : Boolean ): String = {
16
- import scala .collection .JavaConversions ._
17
17
18
- @ tailrec def split (str : String , acc : List [String ]): List [String ] = {
18
+ @ tailrec def splitTokens (str : String , acc : List [String ] = Nil ): List [String ] = {
19
19
if (str == " " ) {
20
20
acc.reverse
21
21
} else {
@@ -30,38 +30,119 @@ object DiffUtil {
30
30
! Character .isMirrored(c) && ! Character .isWhitespace(c)
31
31
}
32
32
}
33
- split (rest, token :: acc)
33
+ splitTokens (rest, token :: acc)
34
34
}
35
35
}
36
36
37
- val lines = split (code, Nil ).toArray
38
- val diff = DiffUtils .diff(split( lastCode, Nil ), lines.toList)
37
+ val tokens = splitTokens (code, Nil ).toArray
38
+ val lastTokens = splitTokens( lastCode, Nil ).toArray
39
39
40
- for (delta <- diff.getDeltas) {
41
- val pos = delta.getRevised.getPosition
42
- val endPos = pos + delta.getRevised.getLines.size - 1
40
+ val diff = hirschberg(lastTokens, tokens)
43
41
44
- delta.getType.toString match { // Issue #1355 forces us to use the toString
45
- case " INSERT" =>
46
- lines(pos) = ADDITION_COLOR + lines(pos)
47
- lines(endPos) = lines(endPos) + ANSI_DEFAULT
42
+ diff.collect {
43
+ case Unmodified (str) => str
44
+ case Inserted (str) => ADDITION_COLOR + str + ANSI_DEFAULT
45
+ case Modified (old, str) if printDiffDel => DELETION_COLOR + old + ADDITION_COLOR + str + ANSI_DEFAULT
46
+ case Modified (_, str) => ADDITION_COLOR + str + ANSI_DEFAULT
47
+ case Deleted (str) if printDiffDel => DELETION_COLOR + str + ANSI_DEFAULT
48
+ }.mkString
49
+ }
48
50
49
- case " CHANGE " =>
50
- val old = if ( ! printDiffDel) " " else
51
- DELETION_COLOR + delta.getOriginal.getLines.mkString + ANSI_DEFAULT
52
- lines(pos) = old + ADDITION_COLOR + lines(pos)
53
- lines(endPos) = lines(endPos) + ANSI_DEFAULT
51
+ private sealed trait Patch
52
+ private final case class Unmodified ( str : String ) extends Patch
53
+ private final case class Modified ( original : String , str : String ) extends Patch
54
+ private final case class Deleted ( str : String ) extends Patch
55
+ private final case class Inserted ( str : String ) extends Patch
54
56
55
- case " DELETE" if printDiffDel =>
56
- val deleted = delta.getOriginal.getLines.mkString
57
- if (! deleted.forall(Character .isWhitespace)) {
58
- lines(pos) = DELETION_COLOR + deleted + ANSI_DEFAULT + lines(pos)
59
- }
57
+ private def hirschberg (a : Array [String ], b : Array [String ]): Array [Patch ] = {
58
+ def build (x : Array [String ], y : Array [String ], builder : mutable.ArrayBuilder [Patch ]): Unit = {
59
+ if (x.isEmpty) {
60
+ builder += Inserted (y.mkString)
61
+ } else if (y.isEmpty) {
62
+ builder += Deleted (x.mkString)
63
+ } else if (x.length == 1 || y.length == 1 ) {
64
+ needlemanWunsch(x, y, builder)
65
+ } else {
66
+ val xlen = x.length
67
+ val xmid = xlen / 2
68
+ val ylen = y.length
69
+
70
+ val (x1, x2) = x.splitAt(xmid)
71
+ val leftScore = nwScore(x1, y)
72
+ val rightScore = nwScore(x2.reverse, y.reverse)
73
+ val scoreSum = (leftScore zip rightScore.reverse).map {
74
+ case (left, right) => left + right
75
+ }
76
+ val max = scoreSum.max
77
+ val ymid = scoreSum.indexOf(max)
78
+
79
+ val (y1, y2) = y.splitAt(ymid)
80
+ build(x1, y1, builder)
81
+ build(x2, y2, builder)
82
+ }
83
+ }
84
+ val builder = Array .newBuilder[Patch ]
85
+ build(a, b, builder)
86
+ builder.result()
87
+ }
60
88
61
- case _ =>
89
+ private def nwScore (x : Array [String ], y : Array [String ]): Array [Int ] = {
90
+ def ins (s : String ) = - 2
91
+ def del (s : String ) = - 2
92
+ def sub (s1 : String , s2 : String ) = if (s1 == s2) 2 else - 1
93
+
94
+ val score = Array .fill(x.length + 1 , y.length + 1 )(0 )
95
+ for (j <- 1 to y.length)
96
+ score(0 )(j) = score(0 )(j - 1 ) + ins(y(j - 1 ))
97
+ for (i <- 1 to x.length) {
98
+ score(i)(0 ) = score(i - 1 )(0 ) + del(x(i - 1 ))
99
+ for (j <- 1 to y.length) {
100
+ val scoreSub = score(i - 1 )(j - 1 ) + sub(x(i - 1 ), y(j - 1 ))
101
+ val scoreDel = score(i - 1 )(j) + del(x(i - 1 ))
102
+ val scoreIns = score(i)(j - 1 ) + ins(y(j - 1 ))
103
+ score(i)(j) = scoreSub max scoreDel max scoreIns
104
+ }
105
+ }
106
+ Array .tabulate(y.length + 1 )(j => score(x.length)(j))
107
+ }
108
+
109
+ private def needlemanWunsch (x : Array [String ], y : Array [String ], builder : mutable.ArrayBuilder [Patch ]): Unit = {
110
+ def similarity (a : String , b : String ) = if (a == b) 2 else - 1
111
+ val d = 1
112
+ val score = Array .tabulate(x.length + 1 , y.length + 1 ) { (i, j) =>
113
+ if (i == 0 ) d * j
114
+ else if (j == 0 ) d * i
115
+ else 0
116
+ }
117
+ for (i <- 1 to x.length) {
118
+ for (j <- 1 to y.length) {
119
+ val mtch = score(i - 1 )(j - 1 ) + similarity(x(i - 1 ), y(j - 1 ))
120
+ val delete = score(i - 1 )(j) + d
121
+ val insert = score(i)(j - 1 ) + d
122
+ score(i)(j) = mtch max insert max delete
62
123
}
63
124
}
64
125
65
- lines.mkString
126
+ var alignment = List .empty[Patch ]
127
+ var i = x.length
128
+ var j = y.length
129
+ while (i > 0 || j > 0 ) {
130
+ if (i > 0 && j > 0 && score(i)(j) == score(i - 1 )(j - 1 ) + similarity(x(i - 1 ), y(j - 1 ))) {
131
+ val newHead =
132
+ if (x(i - 1 ) == y(j - 1 )) Unmodified (x(i - 1 ))
133
+ else Modified (x(i - 1 ), y(j - 1 ))
134
+ alignment = newHead :: alignment
135
+ i = i - 1
136
+ j = j - 1
137
+ } else if (i > 0 && score(i)(j) == score(i - 1 )(j) + d) {
138
+ alignment = Deleted (x(i - 1 )) :: alignment
139
+ i = i - 1
140
+ } else {
141
+ alignment = Inserted (y(j - 1 )) :: alignment
142
+ j = j - 1
143
+ }
144
+ }
145
+ builder ++= alignment
66
146
}
147
+
67
148
}
0 commit comments