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