11package dotty .tools .dotc .util
22
33import scala .annotation .tailrec
4- import difflib ._
4+
5+ import scala .collection .mutable
56
67object DiffUtil {
78
@@ -13,9 +14,8 @@ object DiffUtil {
1314 private final val ADDITION_COLOR = ANSI_GREEN
1415
1516 def mkColoredCodeDiff (code : String , lastCode : String , printDiffDel : Boolean ): String = {
16- import scala .collection .JavaConversions ._
1717
18- @ tailrec def split (str : String , acc : List [String ]): List [String ] = {
18+ @ tailrec def splitTokens (str : String , acc : List [String ] = Nil ): List [String ] = {
1919 if (str == " " ) {
2020 acc.reverse
2121 } else {
@@ -30,38 +30,117 @@ object DiffUtil {
3030 ! Character .isMirrored(c) && ! Character .isWhitespace(c)
3131 }
3232 }
33- split (rest, token :: acc)
33+ splitTokens (rest, token :: acc)
3434 }
3535 }
3636
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
3939
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)
4341
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+ }
4850
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
5456
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)
6076
61- case _ =>
77+ val (y1, y2) = y.splitAt(ymid)
78+ build(x1, y1, builder)
79+ build(x2, y2, builder)
6280 }
6381 }
82+ val builder = Array .newBuilder[Patch ]
83+ build(a, b, builder)
84+ builder.result()
85+ }
86+
87+ private def nwScore (x : Array [String ], y : Array [String ]): Array [Int ] = {
88+ def ins (s : String ) = - 2
89+ def del (s : String ) = - 2
90+ def sub (s1 : String , s2 : String ) = if (s1 == s2) 2 else - 1
6491
65- lines.mkString
92+ val score = Array .fill(x.length + 1 , y.length + 1 )(0 )
93+ for (j <- 1 to y.length)
94+ score(0 )(j) = score(0 )(j - 1 ) + ins(y(j - 1 ))
95+ for (i <- 1 to x.length) {
96+ score(i)(0 ) = score(i - 1 )(0 ) + del(x(i - 1 ))
97+ for (j <- 1 to y.length) {
98+ val scoreSub = score(i - 1 )(j - 1 ) + sub(x(i - 1 ), y(j - 1 ))
99+ val scoreDel = score(i - 1 )(j) + del(x(i - 1 ))
100+ val scoreIns = score(i)(j - 1 ) + ins(y(j - 1 ))
101+ score(i)(j) = scoreSub max scoreDel max scoreIns
102+ }
103+ }
104+ Array .tabulate(y.length + 1 )(j => score(x.length)(j))
66105 }
106+
107+ private def needlemanWunsch (x : Array [String ], y : Array [String ], builder : mutable.ArrayBuilder [Patch ]): Unit = {
108+ def similarity (a : String , b : String ) = if (a == b) 2 else - 1
109+ val d = 1
110+ val F = Array .tabulate(x.length + 1 , y.length + 1 ) { (i, j) =>
111+ if (i == 0 ) d * j
112+ else if (j == 0 ) d * i
113+ else 0
114+ }
115+ for (i <- 1 to x.length) {
116+ for (j <- 1 to y.length) {
117+ val mtch = F (i - 1 )(j - 1 ) + similarity(x(i - 1 ), y(j - 1 ))
118+ val delete = F (i - 1 )(j) + d
119+ val insert = F (i)(j - 1 ) + d
120+ F (i)(j) = mtch max insert max delete
121+ }
122+ }
123+
124+ var alignment = List .empty[Patch ]
125+ var i = x.length
126+ var j = y.length
127+ while (i > 0 || j > 0 ) {
128+ if (i > 0 && j > 0 && F (i)(j) == F (i - 1 )(j - 1 ) + similarity(x(i - 1 ), y(j - 1 ))) {
129+ alignment = {
130+ if (x(i - 1 ) == y(j - 1 )) Unmodified (x(i - 1 ))
131+ else Modified (x(i - 1 ), y(j - 1 ))
132+ } :: alignment
133+ i = i - 1
134+ j = j - 1
135+ } else if (i > 0 && F (i)(j) == F (i - 1 )(j) + d) {
136+ alignment = Deleted (x(i - 1 )) :: alignment
137+ i = i - 1
138+ } else {
139+ alignment = Inserted (y(j - 1 )) :: alignment
140+ j = j - 1
141+ }
142+ }
143+ builder ++= alignment
144+ }
145+
67146}
0 commit comments