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,120 @@ 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 ScoreL = nwScore(x1, y)
72
+ val ScoreR = nwScore(x2.reverse, y.reverse)
73
+ val scoreSum = (ScoreL zip ScoreR .reverse).map(tup => tup._1 + tup._2)
74
+ val max = scoreSum.max
75
+ val ymid = scoreSum.indexOf(max)
60
76
61
- case _ =>
77
+ val (y1, y2) = y.splitAt(ymid)
78
+ build(x1, y1, builder)
79
+ build(x2, y2, builder)
62
80
}
63
81
}
82
+ val builder = Array .newBuilder[Patch ]
83
+ build(a, b, builder)
84
+ builder.result()
85
+ }
64
86
65
- lines.mkString
87
+ private def nwScore (x : Array [String ], y : Array [String ]): Array [Int ] = {
88
+ val score = Array .fill(x.length + 1 , y.length + 1 )(0 )
89
+ for (j <- 1 to y.length)
90
+ score(0 )(j) = score(0 )(j- 1 ) + ins(y(j- 1 ))
91
+ for (i <- 1 to x.length) {
92
+ score(i)(0 ) = score(i - 1 )(0 ) + del(x(i- 1 ))
93
+ for (j <- 1 to y.length) {
94
+ val scoreSub = score(i - 1 )(j - 1 ) + sub(x(i- 1 ), y(j- 1 ))
95
+ val scoreDel = score(i - 1 )(j) + del(x(i- 1 ))
96
+ val scoreIns = score(i)(j - 1 ) + ins(y(j- 1 ))
97
+ score(i)(j) = scoreSub max scoreDel max scoreIns
98
+ }
99
+ }
100
+ Array .tabulate(y.length + 1 )(j => score(x.length)(j))
66
101
}
102
+
103
+ private def needlemanWunsch (x : Array [String ], y : Array [String ], builder : mutable.ArrayBuilder [Patch ]): Unit = {
104
+ val d = 1
105
+ val F = Array .fill(x.length + 1 , y.length + 1 )(0 )
106
+ for (i <- x.indices)
107
+ F (i)(0 ) = d * i
108
+ for (j <- y.indices)
109
+ F (0 )(j) = d * j
110
+ for (i <- 1 to x.length) {
111
+ for (j <- 1 to y.length) {
112
+ val mtch = F (i - 1 )(j - 1 ) + similarity(x(i- 1 ), y(j- 1 ))
113
+ val delete = F (i - 1 )(j) + d
114
+ val insert = F (i)(j - 1 ) + d
115
+ F (i)(j) = mtch max insert max delete
116
+ }
117
+ }
118
+
119
+ var alignment = List .empty[Patch ]
120
+ var i = x.length
121
+ var j = y.length
122
+ while (i > 0 || j > 0 ) {
123
+ if (i > 0 && j > 0 && F (i)(j) == F (i- 1 )(j- 1 ) + similarity(x(i- 1 ), y(j- 1 ))) {
124
+ alignment = {
125
+ if (x(i - 1 ) == y(j - 1 )) Unmodified (x(i - 1 ))
126
+ else Modified (x(i - 1 ), y(j - 1 ))
127
+ } :: alignment
128
+ i = i - 1
129
+ j = j - 1
130
+ } else if (i > 0 && F (i)(j) == F (i- 1 )(j) + d) {
131
+ alignment = Deleted (x(i- 1 )) :: alignment
132
+ i = i - 1
133
+ } else {
134
+ alignment = Inserted (y(j- 1 )) :: alignment
135
+ j = j - 1
136
+ }
137
+ }
138
+ builder ++= alignment
139
+ }
140
+
141
+ private def similarity (a : String , b : String ) = {
142
+ if (a == b) 2
143
+ else - 1
144
+ }
145
+
146
+ private def ins (s : String ) = - 2
147
+ private def del (s : String ) = - 2
148
+ private def sub (s1 : String , s2 : String ) = if (s1 == s2) 2 else - 1
67
149
}
0 commit comments