Highlight line-level (aka word) differences in files

We now highlight any changed words within a line replace edit,
making the actual changes stand out against the surrounding context
that makes up the line.

The highlight is computed by constructing a string that covers the
entire replaced region and then running the Myers diff algorithm
over the individual characters of those two regions.

To avoid tiny edits interleaved at every other character in a
sentance we combine two neighboring character edits together if
there are only 1 or 2 characters between them.  There are probably
many ways to improve on this algorithm to avoid some nasty corner
display cases, but this rule is good enough for now.

The highlight data is computed and stored as part of the diff cache,
which requires a schema change in this commit.  So existing diff
cache records will be flushed on the next server start, and they
will be recomputed on demand.

Bug: issue 169
Change-Id: I69142ebef600e8c3c65821272dad3ee04a497654
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2010-01-30 19:41:17 -08:00
parent 0e4f33b82f
commit 7dafe19aee
14 changed files with 482 additions and 45 deletions

View File

@@ -17,5 +17,6 @@
<source path='diff' includes='
Edit.java
Edit_JsonSerializer.java
ReplaceEdit.java
'/>
</module>

View File

@@ -25,6 +25,8 @@ import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class EditDeserializer implements JsonDeserializer<Edit>,
JsonSerializer<Edit> {
@@ -34,14 +36,28 @@ public class EditDeserializer implements JsonDeserializer<Edit>,
return null;
}
if (!json.isJsonArray()) {
throw new JsonParseException("Expected array of 4for Edit type");
throw new JsonParseException("Expected array for Edit type");
}
final JsonArray a = (JsonArray) json;
if (a.size() != 4) {
final JsonArray o = (JsonArray) json;
final int cnt = o.size();
if (cnt < 4 || cnt % 4 != 0) {
throw new JsonParseException("Expected array of 4 for Edit type");
}
return new Edit(get(a, 0), get(a, 1), get(a, 2), get(a, 3));
if (4 == cnt) {
return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
}
List<Edit> l = new ArrayList<Edit>((cnt / 4) - 1);
for (int i = 4; i < cnt;) {
int as = get(o, i++);
int ae = get(o, i++);
int bs = get(o, i++);
int be = get(o, i++);
l.add(new Edit(as, ae, bs, be));
}
return new ReplaceEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3), l);
}
private static int get(final JsonArray a, final int idx)
@@ -63,10 +79,19 @@ public class EditDeserializer implements JsonDeserializer<Edit>,
return new JsonNull();
}
final JsonArray a = new JsonArray();
add(a, src);
if (src instanceof ReplaceEdit) {
for (Edit e : ((ReplaceEdit) src).getInternalEdits()) {
add(a, e);
}
}
return a;
}
private void add(final JsonArray a, final Edit src) {
a.add(new JsonPrimitive(src.getBeginA()));
a.add(new JsonPrimitive(src.getEndA()));
a.add(new JsonPrimitive(src.getBeginB()));
a.add(new JsonPrimitive(src.getEndB()));
return a;
}
}

View File

@@ -17,6 +17,9 @@ package org.eclipse.jgit.diff;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwtjsonrpc.client.impl.JsonSerializer;
import java.util.ArrayList;
import java.util.List;
public class Edit_JsonSerializer extends JsonSerializer<Edit> {
public static final Edit_JsonSerializer INSTANCE = new Edit_JsonSerializer();
@@ -25,13 +28,38 @@ public class Edit_JsonSerializer extends JsonSerializer<Edit> {
if (jso == null) {
return null;
}
final JavaScriptObject o = (JavaScriptObject) jso;
return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
final int cnt = length(o);
if (4 == cnt) {
return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
}
List<Edit> l = new ArrayList<Edit>((cnt / 4) - 1);
for (int i = 4; i < cnt;) {
int as = get(o, i++);
int ae = get(o, i++);
int bs = get(o, i++);
int be = get(o, i++);
l.add(new Edit(as, ae, bs, be));
}
return new ReplaceEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3), l);
}
@Override
public void printJson(final StringBuilder sb, final Edit o) {
sb.append('[');
append(sb, o);
if (o instanceof ReplaceEdit) {
for (Edit e : ((ReplaceEdit) o).getInternalEdits()) {
sb.append(',');
append(sb, e);
}
}
sb.append(']');
}
private void append(final StringBuilder sb, final Edit o) {
sb.append(o.getBeginA());
sb.append(',');
sb.append(o.getEndA());
@@ -39,9 +67,11 @@ public class Edit_JsonSerializer extends JsonSerializer<Edit> {
sb.append(o.getBeginB());
sb.append(',');
sb.append(o.getEndB());
sb.append(']');
}
private static native int length(JavaScriptObject jso)
/*-{ return jso.length; }-*/;
private static native int get(JavaScriptObject jso, int idx)
/*-{ return jso[idx]; }-*/;
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.eclipse.jgit.diff;
import java.util.List;
public class ReplaceEdit extends Edit {
private List<Edit> internalEdit;
public ReplaceEdit(int as, int ae, int bs, int be, List<Edit> internal) {
super(as, ae, bs, be);
internalEdit = internal;
}
public ReplaceEdit(Edit orig, List<Edit> internal) {
super(orig.getBeginA(), orig.getEndA(), orig.getBeginB(), orig.getEndB());
internalEdit = internal;
}
public List<Edit> getInternalEdits() {
return internalEdit;
}
}