diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java new file mode 100644 index 0000000000..ee28ece036 --- /dev/null +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java @@ -0,0 +1,344 @@ +// Copyright (C) 2013 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 com.google.gerrit.client.diff; + +import com.google.gerrit.client.diff.DiffInfo.Region; +import com.google.gerrit.client.diff.DiffInfo.Span; +import com.google.gerrit.client.diff.PaddingManager.LinePaddingWidgetWrapper; +import com.google.gerrit.client.rpc.Natives; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Unit; + +import net.codemirror.lib.CodeMirror; +import net.codemirror.lib.CodeMirror.LineClassWhere; +import net.codemirror.lib.CodeMirror.LineHandle; +import net.codemirror.lib.Configuration; +import net.codemirror.lib.LineCharacter; +import net.codemirror.lib.TextMarker; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Colors modified regions for {@link SideBySide2}. */ +class ChunkManager { + private final SideBySide2 host; + private final CodeMirror cmA; + private final CodeMirror cmB; + private final SidePanel sidePanel; + private final LineMapper mapper; + + private List chunks; + private List markers; + private List undo; + private Map paddingOnOtherSide; + + ChunkManager(SideBySide2 host, + CodeMirror cmA, + CodeMirror cmB, + SidePanel sidePanel) { + this.host = host; + this.cmA = cmA; + this.cmB = cmB; + this.sidePanel = sidePanel; + this.mapper = new LineMapper(); + } + + LineMapper getLineMapper() { + return mapper; + } + + DiffChunkInfo getFirst() { + return chunks.isEmpty() ? null : chunks.get(0); + } + + void reset() { + mapper.reset(); + for (TextMarker m : markers) { + m.clear(); + } + for (Runnable r : undo) { + r.run(); + } + for (LinePaddingWidgetWrapper x : paddingOnOtherSide.values()) { + x.getWidget().clear(); + } + } + + void render(DiffInfo diff) { + chunks = new ArrayList(); + markers = new ArrayList(); + undo = new ArrayList(); + paddingOnOtherSide = new HashMap(); + + String diffColor = diff.meta_a() == null || diff.meta_b() == null + ? DiffTable.style.intralineBg() + : DiffTable.style.diff(); + + for (Region current : Natives.asList(diff.content())) { + if (current.ab() != null) { + mapper.appendCommon(current.ab().length()); + } else { + render(current, diffColor); + } + } + } + + private void render(Region region, String diffColor) { + int startA = mapper.getLineA(); + int startB = mapper.getLineB(); + + JsArrayString a = region.a(); + JsArrayString b = region.b(); + int aLen = a != null ? a.length() : 0; + int bLen = b != null ? b.length() : 0; + + String color = a == null || b == null + ? diffColor + : DiffTable.style.intralineBg(); + + colorLines(cmA, color, startA, aLen); + colorLines(cmB, color, startB, bLen); + markEdit(cmA, startA, a, region.edit_a()); + markEdit(cmB, startB, b, region.edit_b()); + addGutterTag(region, startA, startB); + mapper.appendReplace(aLen, bLen); + + int endA = mapper.getLineA() - 1; + int endB = mapper.getLineB() - 1; + if (aLen > 0) { + addDiffChunkAndPadding(cmB, endB, endA, aLen, bLen > 0); + } + if (bLen > 0) { + addDiffChunkAndPadding(cmA, endA, endB, bLen, aLen > 0); + } + } + + private void addGutterTag(Region region, int startA, int startB) { + if (region.a() == null) { + sidePanel.addGutter(cmB, startB, SidePanel.GutterType.INSERT); + } else if (region.b() == null) { + sidePanel.addGutter(cmA, startA, SidePanel.GutterType.DELETE); + } else { + sidePanel.addGutter(cmB, startB, SidePanel.GutterType.EDIT); + } + } + + private void markEdit(CodeMirror cm, int startLine, + JsArrayString lines, JsArray edits) { + if (lines == null || edits == null) { + return; + } + + EditIterator iter = new EditIterator(lines, startLine); + Configuration bg = Configuration.create() + .set("className", DiffTable.style.intralineBg()) + .set("readOnly", true); + + Configuration diff = Configuration.create() + .set("className", DiffTable.style.diff()) + .set("readOnly", true); + + LineCharacter last = CodeMirror.pos(0, 0); + for (Span span : Natives.asList(edits)) { + LineCharacter from = iter.advance(span.skip()); + LineCharacter to = iter.advance(span.mark()); + if (from.getLine() == last.getLine()) { + markers.add(cm.markText(last, from, bg)); + } else { + markers.add(cm.markText(CodeMirror.pos(from.getLine(), 0), from, bg)); + } + markers.add(cm.markText(from, to, diff)); + last = to; + colorLines(cm, LineClassWhere.BACKGROUND, + DiffTable.style.diff(), + from.getLine(), to.getLine()); + } + } + + private void colorLines(CodeMirror cm, String color, int line, int cnt) { + colorLines(cm, LineClassWhere.WRAP, color, line, line + cnt); + } + + private void colorLines(final CodeMirror cm, final LineClassWhere where, + final String className, final int start, final int end) { + if (start < end) { + for (int line = start; line < end; line++) { + cm.addLineClass(line, where, className); + } + undo.add(new Runnable() { + @Override + public void run() { + for (int line = start; line < end; line++) { + cm.removeLineClass(line, where, className); + } + } + }); + } + } + + private void addDiffChunkAndPadding(CodeMirror cmToPad, int lineToPad, + int lineOnOther, int chunkSize, boolean edit) { + CodeMirror otherCm = host.otherCm(cmToPad); + paddingOnOtherSide.put(otherCm.getLineHandle(lineOnOther), + new LinePaddingWidgetWrapper(host.addPaddingWidget(cmToPad, + lineToPad, 0, Unit.EM, null), lineToPad, chunkSize)); + chunks.add(new DiffChunkInfo(otherCm.side(), + lineOnOther - chunkSize + 1, lineOnOther, edit)); + } + + Runnable diffChunkNav(final CodeMirror cm, final Direction dir) { + return new Runnable() { + @Override + public void run() { + int line = cm.hasActiveLine() ? cm.getLineNumber(cm.getActiveLine()) : 0; + int res = Collections.binarySearch( + chunks, + new DiffChunkInfo(cm.side(), line, 0, false), + getDiffChunkComparator()); + if (res < 0) { + res = -res - (dir == Direction.PREV ? 1 : 2); + } + res = res + (dir == Direction.PREV ? -1 : 1); + if (res < 0 || chunks.size() <= res) { + return; + } + + DiffChunkInfo lookUp = chunks.get(res); + // If edit, skip the deletion chunk and set focus on the insertion one. + if (lookUp.isEdit() && lookUp.getSide() == DisplaySide.A) { + res = res + (dir == Direction.PREV ? -1 : 1); + if (res < 0 || chunks.size() <= res) { + return; + } + } + + DiffChunkInfo target = chunks.get(res); + CodeMirror targetCm = host.getCmFromSide(target.getSide()); + targetCm.setCursor(LineCharacter.create(target.getStart())); + targetCm.focus(); + targetCm.scrollToY( + targetCm.heightAtLine(target.getStart(), "local") - + 0.5 * cmB.getScrollbarV().getClientHeight()); + } + }; + } + + private Comparator getDiffChunkComparator() { + // Chunks are ordered by their starting line. If it's a deletion, + // use its corresponding line on the revision side for comparison. + // In the edit case, put the deletion chunk right before the + // insertion chunk. This placement guarantees well-ordering. + return new Comparator() { + @Override + public int compare(DiffChunkInfo a, DiffChunkInfo b) { + if (a.getSide() == b.getSide()) { + return a.getStart() - b.getStart(); + } else if (a.getSide() == DisplaySide.A) { + int comp = mapper.lineOnOther(a.getSide(), a.getStart()) + .getLine() - b.getStart(); + return comp == 0 ? -1 : comp; + } else { + int comp = a.getStart() - + mapper.lineOnOther(b.getSide(), b.getStart()).getLine(); + return comp == 0 ? 1 : comp; + } + } + }; + } + + DiffChunkInfo getDiffChunk(DisplaySide side, int line) { + int res = Collections.binarySearch( + chunks, + new DiffChunkInfo(side, line, 0, false), // Dummy DiffChunkInfo + getDiffChunkComparator()); + if (res >= 0) { + return chunks.get(res); + } else { // The line might be within a DiffChunk + res = -res - 1; + if (res > 0) { + DiffChunkInfo info = chunks.get(res - 1); + if (info.getSide() == side && info.getStart() <= line && + line <= info.getEnd()) { + return info; + } + } + } + return null; + } + + void resizePadding(final CodeMirror cm, + final LineHandle line, + final DisplaySide side) { + if (paddingOnOtherSide.containsKey(line)) { + host.defer(new Runnable() { + @Override + public void run() { + resizePaddingOnOtherSide(side, cm.getLineNumber(line)); + } + }); + } + } + + void resizePaddingOnOtherSide(DisplaySide mySide, int line) { + CodeMirror cm = host.getCmFromSide(mySide); + LineHandle handle = cm.getLineHandle(line); + final LinePaddingWidgetWrapper otherWrapper = paddingOnOtherSide.get(handle); + double myChunkHeight = cm.heightAtLine(line + 1) - + cm.heightAtLine(line - otherWrapper.getChunkLength() + 1); + Element otherPadding = otherWrapper.getElement(); + int otherPaddingHeight = otherPadding.getOffsetHeight(); + CodeMirror otherCm = host.otherCm(cm); + int otherLine = otherWrapper.getOtherLine(); + LineHandle other = otherCm.getLineHandle(otherLine); + if (paddingOnOtherSide.containsKey(other)) { + LinePaddingWidgetWrapper myWrapper = paddingOnOtherSide.get(other); + Element myPadding = paddingOnOtherSide.get(other).getElement(); + int myPaddingHeight = myPadding.getOffsetHeight(); + myChunkHeight -= myPaddingHeight; + double otherChunkHeight = otherCm.heightAtLine(otherLine + 1) - + otherCm.heightAtLine(otherLine - myWrapper.getChunkLength() + 1) - + otherPaddingHeight; + double delta = myChunkHeight - otherChunkHeight; + if (delta > 0) { + if (myPaddingHeight != 0) { + myPadding.getStyle().setHeight((double) 0, Unit.PX); + myWrapper.getWidget().changed(); + } + if (otherPaddingHeight != delta) { + otherPadding.getStyle().setHeight(delta, Unit.PX); + otherWrapper.getWidget().changed(); + } + } else { + if (myPaddingHeight != -delta) { + myPadding.getStyle().setHeight(-delta, Unit.PX); + myWrapper.getWidget().changed(); + } + if (otherPaddingHeight != 0) { + otherPadding.getStyle().setHeight((double) 0, Unit.PX); + otherWrapper.getWidget().changed(); + } + } + } else if (otherPaddingHeight != myChunkHeight) { + otherPadding.getStyle().setHeight(myChunkHeight, Unit.PX); + otherWrapper.getWidget().changed(); + } + } +} diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java index a44802eac2..83d2f99f03 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java @@ -85,9 +85,8 @@ abstract class CommentBox extends Composite { assert selfWidgetWrapper != null; selfWidgetWrapper.getWidget().changed(); if (diffChunkInfo != null) { - commentManager.getSideBySide2().resizePaddingOnOtherSide( - cm.side(), - diffChunkInfo.getEnd()); + commentManager.getSideBySide2().getChunkManager() + .resizePaddingOnOtherSide(cm.side(), diffChunkInfo.getEnd()); } else { assert widgetManager != null; widgetManager.resizePaddingWidget(); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java index 57d4f65907..04bd1da408 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java @@ -337,8 +337,9 @@ class CommentManager { int lineToPad = host.lineOnOther(cm.side(), line).getLine(); LineHandle otherHandle = other.getLineHandle(lineToPad); - DiffChunkInfo myChunk = host.getDiffChunk(cm.side(), line); - DiffChunkInfo otherChunk = host.getDiffChunk(other.side(), lineToPad); + ChunkManager chunkMgr = host.getChunkManager(); + DiffChunkInfo myChunk = chunkMgr.getDiffChunk(cm.side(), line); + DiffChunkInfo otherChunk = chunkMgr.getDiffChunk(other.side(), lineToPad); PaddingManager otherManager; if (linePaddingManager.containsKey(otherHandle)) { otherManager = linePaddingManager.get(otherHandle); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java index 332a793cd2..9cfa04d46d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java @@ -22,6 +22,7 @@ import com.google.gerrit.client.changes.ChangeInfo; import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo; import com.google.gerrit.client.changes.ReviewInfo; import com.google.gerrit.client.changes.Util; +import com.google.gerrit.client.diff.DiffInfo.Region; import com.google.gerrit.client.patches.PatchUtil; import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.GerritCallback; @@ -313,7 +314,10 @@ class Header extends Composite { return nextPath; } - void setNoDiff(boolean visible) { - UIObject.setVisible(noDiff, visible); + void setNoDiff(DiffInfo diff) { + JsArray regions = diff.content(); + boolean b = regions.length() == 0 + || (regions.length() == 1 && regions.get(0).ab() != null); + UIObject.setVisible(noDiff, b); } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java index 7a5ce5d553..968b1f6af6 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java @@ -26,6 +26,12 @@ class LineMapper { private List lineMapBtoA; LineMapper() { + reset(); + } + + void reset() { + lineA = 0; + lineB = 0; lineMapAtoB = new ArrayList(); lineMapBtoA = new ArrayList(); } @@ -43,6 +49,17 @@ class LineMapper { lineB += numLines; } + void appendReplace(int aLen, int bLen) { + appendCommon(Math.min(aLen, bLen)); + if (aLen < bLen) { // Edit with insertion + int insertCnt = bLen - aLen; + appendInsert(insertCnt); + } else if (aLen > bLen) { // Edit with deletion + int deleteCnt = aLen - bLen; + appendDelete(deleteCnt); + } + } + void appendInsert(int numLines) { int origLineB = lineB; lineB += numLines; diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java index ccf216cd62..2128f0781b 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java @@ -21,10 +21,7 @@ import com.google.gerrit.client.changes.ChangeApi; import com.google.gerrit.client.changes.ChangeInfo; import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo; import com.google.gerrit.client.changes.ChangeList; -import com.google.gerrit.client.diff.DiffInfo.Region; -import com.google.gerrit.client.diff.DiffInfo.Span; import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo; -import com.google.gerrit.client.diff.PaddingManager.LinePaddingWidgetWrapper; import com.google.gerrit.client.diff.PaddingManager.PaddingWidgetWrapper; import com.google.gerrit.client.patches.PatchUtil; import com.google.gerrit.client.projects.ConfigInfoCache; @@ -38,9 +35,7 @@ import com.google.gerrit.common.changes.ListChangesOption; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; -import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -81,23 +76,15 @@ import net.codemirror.lib.LineCharacter; import net.codemirror.lib.LineWidget; import net.codemirror.lib.ModeInjector; import net.codemirror.lib.Rect; -import net.codemirror.lib.TextMarker; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class SideBySide2 extends Screen { interface Binder extends UiBinder {} private static final Binder uiBinder = GWT.create(Binder.class); - private static final JsArrayString EMPTY = - JavaScriptObject.createArray().cast(); - @UiField(provided = true) Header header; @@ -108,8 +95,8 @@ public class SideBySide2 extends Screen { private final PatchSet.Id base; private final PatchSet.Id revision; private final String path; - private final DisplaySide startSide; - private final int startLine; + private DisplaySide startSide; + private int startLine; private DiffPreferences prefs; private CodeMirror cmA; @@ -117,13 +104,9 @@ public class SideBySide2 extends Screen { private HandlerRegistration resizeHandler; private DiffInfo diff; private boolean largeFile; - private LineMapper mapper; - private List markers; - private List undoLineClass; + private ChunkManager chunkManager; private CommentManager commentManager; private SkipManager skipManager; - private Map linePaddingOnOtherSideMap; - private List diffChunks; private KeyCommandSet keysNavigation; private KeyCommandSet keysAction; @@ -250,6 +233,13 @@ public class SideBySide2 extends Screen { }); diffTable.sidePanel.adjustGutters(cmB); + if (startSide == null && diff.meta_b() != null) { + DiffChunkInfo d = chunkManager.getFirst(); + if (d != null) { + startSide = d.getSide(); + startLine = d.getStart() + 1; + } + } if (startSide != null && startLine > 0) { int line = startLine - 1; CodeMirror cm = getCmFromSide(startSide); @@ -258,18 +248,6 @@ public class SideBySide2 extends Screen { } cm.setCursor(LineCharacter.create(line)); cm.focus(); - } else if (diff.meta_b() != null) { - int line = 0; - if (!diffChunks.isEmpty()) { - DiffChunkInfo d = diffChunks.get(0); - CodeMirror cm = getCmFromSide(d.getSide()); - line = d.getStart(); - if (cm.lineAtHeight(height - 20) < line) { - cm.scrollToY(cm.heightAtLine(line, "local") - 0.5 * height); - } - } - cmB.setCursor(LineCharacter.create(line)); - cmB.focus(); } else { cmA.setCursor(LineCharacter.create(0)); cmA.focus(); @@ -335,7 +313,7 @@ public class SideBySide2 extends Screen { .on("Enter", commentManager.toggleOpenBox(cm)) .on("'c'", commentManager.insertNewDraft(cm)) .on("N", maybeNextVimSearch(cm)) - .on("P", diffChunkNav(cm, Direction.PREV)) + .on("P", chunkManager.diffChunkNav(cm, Direction.PREV)) .on("Shift-O", commentManager.openClosePublished(cm)) .on("Shift-Left", moveCursorToSide(cm, DisplaySide.A)) .on("Shift-Right", moveCursorToSide(cm, DisplaySide.B)) @@ -491,6 +469,7 @@ public class SideBySide2 extends Screen { cmA = newCM(diff.meta_a(), diff.text_a(), DisplaySide.A, diffTable.cmA); cmB = newCM(diff.meta_b(), diff.text_b(), DisplaySide.B, diffTable.cmB); + chunkManager = new ChunkManager(this, cmA, cmB, diffTable.sidePanel); skipManager = new SkipManager(this, commentManager); operation(new Runnable() { @@ -512,7 +491,7 @@ public class SideBySide2 extends Screen { registerCmEvents(cmA); registerCmEvents(cmB); - new ScrollSynchronizer(diffTable, cmA, cmB, mapper); + new ScrollSynchronizer(diffTable, cmA, cmB, chunkManager.getLineMapper()); prefsAction = new PreferencesAction(this, prefs); header.init(prefsAction); @@ -535,7 +514,7 @@ public class SideBySide2 extends Screen { String contents, DisplaySide side, Element parent) { - Configuration cfg = Configuration.create() + return CodeMirror.create(side, parent, Configuration.create() .set("readOnly", true) .set("cursorBlinkRate", 0) .set("cursorHeight", 0.85) @@ -546,8 +525,7 @@ public class SideBySide2 extends Screen { .set("styleSelectedText", true) .set("showTrailingSpace", prefs.showWhitespaceErrors()) .set("keyMap", "vim_ro") - .set("value", meta != null ? contents : ""); - return CodeMirror.create(side, parent, cfg); + .set("value", meta != null ? contents : "")); } DiffInfo.IntraLineStatus getIntraLineStatus() { @@ -621,86 +599,8 @@ public class SideBySide2 extends Screen { } private void render(DiffInfo diff) { - JsArray regions = diff.content(); - - header.setNoDiff(regions.length() == 0 - || (regions.length() == 1 && regions.get(0).ab() != null)); - - mapper = new LineMapper(); - markers = new ArrayList(); - undoLineClass = new ArrayList(); - linePaddingOnOtherSideMap = new HashMap(); - diffChunks = new ArrayList(); - - String diffColor = diff.meta_a() == null || diff.meta_b() == null - ? DiffTable.style.intralineBg() - : DiffTable.style.diff(); - - for (int i = 0; i < regions.length(); i++) { - Region current = regions.get(i); - int origLineA = mapper.getLineA(); - int origLineB = mapper.getLineB(); - if (current.ab() != null) { // Common - mapper.appendCommon(current.ab().length()); - } else { // Insert, Delete or Edit - JsArrayString currentA = current.a() == null ? EMPTY : current.a(); - JsArrayString currentB = current.b() == null ? EMPTY : current.b(); - int aLength = currentA.length(); - int bLength = currentB.length(); - String color = currentA == EMPTY || currentB == EMPTY - ? diffColor - : DiffTable.style.intralineBg(); - colorLines(cmA, color, origLineA, aLength); - colorLines(cmB, color, origLineB, bLength); - int commonCnt = Math.min(aLength, bLength); - mapper.appendCommon(commonCnt); - if (aLength < bLength) { // Edit with insertion - int insertCnt = bLength - aLength; - mapper.appendInsert(insertCnt); - } else if (aLength > bLength) { // Edit with deletion - int deleteCnt = aLength - bLength; - mapper.appendDelete(deleteCnt); - } - int chunkEndA = mapper.getLineA() - 1; - int chunkEndB = mapper.getLineB() - 1; - if (aLength > 0) { - addDiffChunkAndPadding(cmB, chunkEndB, chunkEndA, aLength, bLength > 0); - } - if (bLength > 0) { - addDiffChunkAndPadding(cmA, chunkEndA, chunkEndB, bLength, aLength > 0); - } - markEdit(cmA, currentA, current.edit_a(), origLineA); - markEdit(cmB, currentB, current.edit_b(), origLineB); - if (aLength == 0) { - diffTable.sidePanel.addGutter(cmB, origLineB, SidePanel.GutterType.INSERT); - } else if (bLength == 0) { - diffTable.sidePanel.addGutter(cmA, origLineA, SidePanel.GutterType.DELETE); - } else { - diffTable.sidePanel.addGutter(cmB, origLineB, SidePanel.GutterType.EDIT); - } - } - } - } - - private void clearMarkers() { - if (markers != null) { - for (TextMarker m : markers) { - m.clear(); - } - markers = null; - } - if (undoLineClass != null) { - for (Runnable r : undoLineClass) { - r.run(); - } - undoLineClass = null; - } - if (linePaddingOnOtherSideMap != null) { - for (LinePaddingWidgetWrapper x : linePaddingOnOtherSideMap.values()) { - x.getWidget().clear(); - } - linePaddingOnOtherSideMap = null; - } + header.setNoDiff(diff); + chunkManager.render(diff); } CodeMirror otherCm(CodeMirror me) { @@ -712,69 +612,7 @@ public class SideBySide2 extends Screen { } LineOnOtherInfo lineOnOther(DisplaySide side, int line) { - return mapper.lineOnOther(side, line); - } - - private void markEdit(CodeMirror cm, JsArrayString lines, - JsArray edits, int startLine) { - if (edits == null) { - return; - } - EditIterator iter = new EditIterator(lines, startLine); - Configuration intralineBgOpt = Configuration.create() - .set("className", DiffTable.style.intralineBg()) - .set("readOnly", true); - Configuration diffOpt = Configuration.create() - .set("className", DiffTable.style.diff()) - .set("readOnly", true); - LineCharacter last = CodeMirror.pos(0, 0); - for (int i = 0; i < edits.length(); i++) { - Span span = edits.get(i); - LineCharacter from = iter.advance(span.skip()); - LineCharacter to = iter.advance(span.mark()); - int fromLine = from.getLine(); - if (last.getLine() == fromLine) { - markers.add(cm.markText(last, from, intralineBgOpt)); - } else { - markers.add(cm.markText(CodeMirror.pos(fromLine, 0), from, intralineBgOpt)); - } - markers.add(cm.markText(from, to, diffOpt)); - last = to; - colorLines(cm, LineClassWhere.BACKGROUND, - DiffTable.style.diff(), - fromLine, to.getLine()); - } - } - - private void colorLines(CodeMirror cm, String color, int line, int cnt) { - colorLines(cm, LineClassWhere.WRAP, color, line, line + cnt); - } - - private void colorLines(final CodeMirror cm, final LineClassWhere where, - final String className, final int start, final int end) { - if (start < end) { - for (int line = start; line < end; line++) { - cm.addLineClass(line, where, className); - } - undoLineClass.add(new Runnable() { - @Override - public void run() { - for (int line = start; line < end; line++) { - cm.removeLineClass(line, where, className); - } - } - }); - } - } - - private void addDiffChunkAndPadding(CodeMirror cmToPad, int lineToPad, - int lineOnOther, int chunkSize, boolean edit) { - CodeMirror otherCm = otherCm(cmToPad); - linePaddingOnOtherSideMap.put(otherCm.getLineHandle(lineOnOther), - new LinePaddingWidgetWrapper(addPaddingWidget(cmToPad, - lineToPad, 0, Unit.EM, null), lineToPad, chunkSize)); - diffChunks.add(new DiffChunkInfo(otherCm.side(), - lineOnOther - chunkSize + 1, lineOnOther, edit)); + return chunkManager.getLineMapper().lineOnOther(side, line); } PaddingWidgetWrapper addPaddingWidget(CodeMirror cm, @@ -862,7 +700,7 @@ public class SideBySide2 extends Screen { cm.addLineClass( handle, LineClassWhere.WRAP, DiffTable.style.activeLine()); LineOnOtherInfo info = - mapper.lineOnOther(cm.side(), cm.getLineNumber(handle)); + lineOnOther(cm.side(), cm.getLineNumber(handle)); if (info.isAligned()) { LineHandle oLineHandle = other.getLineHandle(info.getLine()); other.setActiveLine(oLineHandle); @@ -933,7 +771,7 @@ public class SideBySide2 extends Screen { return new Runnable() { public void run() { if (cmSrc.hasActiveLine()) { - cmDst.setCursor(LineCharacter.create(mapper.lineOnOther( + cmDst.setCursor(LineCharacter.create(lineOnOther( sideSrc, cmSrc.getLineNumber(cmSrc.getActiveLine())).getLine())); } @@ -949,93 +787,12 @@ public class SideBySide2 extends Screen { if (cm.hasVimSearchHighlight()) { CodeMirror.handleVimKey(cm, "n"); } else { - diffChunkNav(cm, Direction.NEXT).run(); + chunkManager.diffChunkNav(cm, Direction.NEXT).run(); } } }; } - private Runnable diffChunkNav(final CodeMirror cm, final Direction dir) { - return new Runnable() { - @Override - public void run() { - int line = cm.hasActiveLine() ? cm.getLineNumber(cm.getActiveLine()) : 0; - int res = Collections.binarySearch( - diffChunks, - new DiffChunkInfo(cm.side(), line, 0, false), - getDiffChunkComparator()); - if (res < 0) { - res = -res - (dir == Direction.PREV ? 1 : 2); - } - res = res + (dir == Direction.PREV ? -1 : 1); - if (res < 0 || diffChunks.size() <= res) { - return; - } - - DiffChunkInfo lookUp = diffChunks.get(res); - // If edit, skip the deletion chunk and set focus on the insertion one. - if (lookUp.isEdit() && lookUp.getSide() == DisplaySide.A) { - res = res + (dir == Direction.PREV ? -1 : 1); - if (res < 0 || diffChunks.size() <= res) { - return; - } - } - - DiffChunkInfo target = diffChunks.get(res); - CodeMirror targetCm = getCmFromSide(target.getSide()); - targetCm.setCursor(LineCharacter.create(target.getStart())); - targetCm.focus(); - targetCm.scrollToY( - targetCm.heightAtLine(target.getStart(), "local") - - 0.5 * cmB.getScrollbarV().getClientHeight()); - } - }; - } - - /** - * Diff chunks are ordered by their starting lines. If it's a deletion, - * use its corresponding line on the revision side for comparison. In - * the edit case, put the deletion chunk right before the insertion chunk. - * This placement guarantees well-ordering. - */ - private Comparator getDiffChunkComparator() { - return new Comparator() { - @Override - public int compare(DiffChunkInfo o1, DiffChunkInfo o2) { - if (o1.getSide() == o2.getSide()) { - return o1.getStart() - o2.getStart(); - } else if (o1.getSide() == DisplaySide.A) { - int comp = mapper.lineOnOther(o1.getSide(), o1.getStart()) - .getLine() - o2.getStart(); - return comp == 0 ? -1 : comp; - } else { - int comp = o1.getStart() - - mapper.lineOnOther(o2.getSide(), o2.getStart()).getLine(); - return comp == 0 ? 1 : comp; - } - } - }; - } - - DiffChunkInfo getDiffChunk(DisplaySide side, int line) { - int res = Collections.binarySearch( - diffChunks, - new DiffChunkInfo(side, line, 0, false), // Dummy DiffChunkInfo - getDiffChunkComparator()); - if (res >= 0) { - return diffChunks.get(res); - } else { // The line might be within a DiffChunk - res = -res - 1; - if (res > 0) { - DiffChunkInfo info = diffChunks.get(res - 1); - if (info.getSide() == side && info.getStart() <= line && - line <= info.getEnd()) { - return info; - } - } - } - return null; - } void defer(Runnable thunk) { if (deferred == null) { @@ -1058,66 +815,13 @@ public class SideBySide2 extends Screen { deferred.add(thunk); } - void resizePaddingOnOtherSide(DisplaySide mySide, int line) { - CodeMirror cm = getCmFromSide(mySide); - LineHandle handle = cm.getLineHandle(line); - final LinePaddingWidgetWrapper otherWrapper = linePaddingOnOtherSideMap.get(handle); - double myChunkHeight = cm.heightAtLine(line + 1) - - cm.heightAtLine(line - otherWrapper.getChunkLength() + 1); - Element otherPadding = otherWrapper.getElement(); - int otherPaddingHeight = otherPadding.getOffsetHeight(); - CodeMirror otherCm = otherCm(cm); - int otherLine = otherWrapper.getOtherLine(); - LineHandle other = otherCm.getLineHandle(otherLine); - if (linePaddingOnOtherSideMap.containsKey(other)) { - LinePaddingWidgetWrapper myWrapper = linePaddingOnOtherSideMap.get(other); - Element myPadding = linePaddingOnOtherSideMap.get(other).getElement(); - int myPaddingHeight = myPadding.getOffsetHeight(); - myChunkHeight -= myPaddingHeight; - double otherChunkHeight = otherCm.heightAtLine(otherLine + 1) - - otherCm.heightAtLine(otherLine - myWrapper.getChunkLength() + 1) - - otherPaddingHeight; - double delta = myChunkHeight - otherChunkHeight; - if (delta > 0) { - if (myPaddingHeight != 0) { - myPadding.getStyle().setHeight((double) 0, Unit.PX); - myWrapper.getWidget().changed(); - } - if (otherPaddingHeight != delta) { - otherPadding.getStyle().setHeight(delta, Unit.PX); - otherWrapper.getWidget().changed(); - } - } else { - if (myPaddingHeight != -delta) { - myPadding.getStyle().setHeight(-delta, Unit.PX); - myWrapper.getWidget().changed(); - } - if (otherPaddingHeight != 0) { - otherPadding.getStyle().setHeight((double) 0, Unit.PX); - otherWrapper.getWidget().changed(); - } - } - } else if (otherPaddingHeight != myChunkHeight) { - otherPadding.getStyle().setHeight(myChunkHeight, Unit.PX); - otherWrapper.getWidget().changed(); - } - } - // TODO: Maybe integrate this with PaddingManager. private RenderLineHandler resizeLinePadding(final DisplaySide side) { return new RenderLineHandler() { @Override - public void handle(final CodeMirror instance, final LineHandle handle, - Element element) { - commentManager.resizePadding(handle); - if (linePaddingOnOtherSideMap.containsKey(handle)) { - defer(new Runnable() { - @Override - public void run() { - resizePaddingOnOtherSide(side, instance.getLineNumber(handle)); - } - }); - } + public void handle(CodeMirror cm, LineHandle lh, Element e) { + commentManager.resizePadding(lh); + chunkManager.resizePadding(cm, lh, side); } }; } @@ -1156,6 +860,10 @@ public class SideBySide2 extends Screen { return prefs; } + ChunkManager getChunkManager() { + return chunkManager; + } + CommentManager getCommentManager() { return commentManager; } @@ -1214,7 +922,7 @@ public class SideBySide2 extends Screen { @Override public void run() { skipManager.removeAll(); - clearMarkers(); + chunkManager.reset(); diffTable.sidePanel.clearDiffGutters(); setShowIntraline(prefs.intralineDifference()); render(diff);