mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
diff: iterating between differences should make both sides visible
* work around SyncScroll issue with big insertion/deletion at the end of file * move carets in all editors to change
This commit is contained in:
@@ -411,12 +411,19 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doScrollToChange(@NotNull SimpleDiffChange change, boolean animated) {
|
||||
private void doScrollToChange(@NotNull SimpleDiffChange change, final boolean animated) {
|
||||
if (myEditor1 == null || myEditor2 == null) return;
|
||||
assert mySyncScrollSupport != null;
|
||||
|
||||
EditorEx editor = getCurrentEditor();
|
||||
int line = change.getStartLine(getCurrentSide());
|
||||
DiffUtil.scrollEditor(editor, line, animated);
|
||||
final int line1 = change.getStartLine(Side.LEFT);
|
||||
final int line2 = change.getStartLine(Side.RIGHT);
|
||||
final int endLine1 = change.getEndLine(Side.LEFT);
|
||||
final int endLine2 = change.getEndLine(Side.RIGHT);
|
||||
|
||||
DiffUtil.moveCaret(myEditor1, line1);
|
||||
DiffUtil.moveCaret(myEditor2, line2);
|
||||
|
||||
mySyncScrollSupport.makeVisible(getCurrentSide(), line1, endLine1, line2, endLine2, animated);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -346,6 +346,7 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
|
||||
}
|
||||
|
||||
private void doScrollToChange(@NotNull SimpleThreesideDiffChange change, boolean animated) {
|
||||
// TODO: use anchors to fix scrolling issue at the start/end of file
|
||||
EditorEx editor = getCurrentEditor();
|
||||
int line = change.getStartLine(getCurrentSide());
|
||||
DiffUtil.scrollEditor(editor, line, animated);
|
||||
|
||||
@@ -15,15 +15,18 @@
|
||||
*/
|
||||
package com.intellij.diff.tools.util;
|
||||
|
||||
import com.intellij.diff.util.IntPair;
|
||||
import com.intellij.diff.util.Side;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.editor.ScrollingModel;
|
||||
import com.intellij.openapi.editor.event.VisibleAreaEvent;
|
||||
import com.intellij.openapi.editor.event.VisibleAreaListener;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import gnu.trove.TIntFunction;
|
||||
import org.jetbrains.annotations.CalledInAwt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -82,6 +85,51 @@ public class SyncScrollSupport {
|
||||
public boolean isDuringSyncScroll() {
|
||||
return myDuringSyncScroll;
|
||||
}
|
||||
|
||||
public void makeVisible(@NotNull Side masterSide,
|
||||
int startLine1, int endLine1, int startLine2, int endLine2,
|
||||
final boolean animate) {
|
||||
Side slaveSide = masterSide.other();
|
||||
|
||||
final IntPair offsets = getTargetOffsets(myEditor1, myEditor2, startLine1, endLine1, startLine2, endLine2);
|
||||
|
||||
final Editor masterEditor = masterSide.select(myEditor1, myEditor2);
|
||||
final Editor slaveEditor = slaveSide.select(myEditor1, myEditor2);
|
||||
|
||||
final int masterOffset = masterSide.select(offsets.val1, offsets.val2);
|
||||
final int slaveOffset = slaveSide.select(offsets.val1, offsets.val2);
|
||||
|
||||
int startOffset1 = myEditor1.getScrollingModel().getVisibleArea().y;
|
||||
int startOffset2 = myEditor2.getScrollingModel().getVisibleArea().y;
|
||||
final int masterStartOffset = masterSide.select(startOffset1, startOffset2);
|
||||
|
||||
myHelper1.setAnchor(startOffset1, offsets.val1, startOffset2, offsets.val2);
|
||||
myHelper2.setAnchor(startOffset2, offsets.val2, startOffset1, offsets.val1);
|
||||
|
||||
doScrollHorizontally(masterEditor, 0, false); // animation will be canceled by "scroll vertically" anyway
|
||||
doScrollVertically(masterEditor, masterOffset, animate);
|
||||
|
||||
masterEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myHelper1.removeAnchor();
|
||||
myHelper2.removeAnchor();
|
||||
|
||||
if (masterOffset == masterStartOffset) { // master editor didn't scrolled
|
||||
myDuringSyncScroll = true;
|
||||
|
||||
doScrollVertically(slaveEditor, slaveOffset, animate);
|
||||
|
||||
slaveEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myDuringSyncScroll = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ThreesideSyncScrollSupport {
|
||||
@@ -157,78 +205,150 @@ public class SyncScrollSupport {
|
||||
private static class MyScrollHelper implements VisibleAreaListener {
|
||||
@NotNull private final Editor myMaster;
|
||||
@NotNull private final Editor mySlave;
|
||||
|
||||
@NotNull private final TIntFunction myConvertor;
|
||||
|
||||
@Nullable private Anchor myAnchor;
|
||||
|
||||
public MyScrollHelper(@NotNull Editor master, @NotNull Editor slave, @NotNull TIntFunction convertor) {
|
||||
myMaster = master;
|
||||
mySlave = slave;
|
||||
myConvertor = convertor;
|
||||
}
|
||||
|
||||
public void setAnchor(int masterStartOffset, int masterEndOffset, int slaveStartOffset, int slaveEndOffset) {
|
||||
myAnchor = new Anchor(masterStartOffset, masterEndOffset, slaveStartOffset, slaveEndOffset);
|
||||
}
|
||||
|
||||
public void removeAnchor() {
|
||||
myAnchor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visibleAreaChanged(VisibleAreaEvent e) {
|
||||
Rectangle newRectangle = e.getNewRectangle();
|
||||
Rectangle oldRectangle = e.getOldRectangle();
|
||||
if (oldRectangle == null) return;
|
||||
|
||||
syncVerticalScroll(newRectangle, oldRectangle);
|
||||
syncHorizontalScroll(newRectangle, oldRectangle);
|
||||
if (newRectangle.x != oldRectangle.x) syncHorizontalScroll(false);
|
||||
if (newRectangle.y != oldRectangle.y) syncVerticalScroll(false);
|
||||
}
|
||||
|
||||
private void syncVerticalScroll(@NotNull Rectangle newRectangle, @NotNull Rectangle oldRectangle) {
|
||||
if (newRectangle.y == oldRectangle.y) return;
|
||||
|
||||
private void syncVerticalScroll(boolean animated) {
|
||||
if (myMaster.getDocument().getTextLength() == 0) return;
|
||||
|
||||
int masterVerticalScrollOffset = myMaster.getScrollingModel().getVerticalScrollOffset();
|
||||
|
||||
Rectangle viewRect = myMaster.getScrollingModel().getVisibleArea();
|
||||
int middleY = viewRect.height / 3;
|
||||
|
||||
LogicalPosition masterPos = myMaster.xyToLogicalPosition(new Point(viewRect.x, masterVerticalScrollOffset + middleY));
|
||||
int masterCenterLine = masterPos.line;
|
||||
int scrollToLine = myConvertor.execute(masterCenterLine);
|
||||
int offset;
|
||||
if (myAnchor == null) {
|
||||
LogicalPosition masterPos = myMaster.xyToLogicalPosition(new Point(viewRect.x, viewRect.y + middleY));
|
||||
int masterCenterLine = masterPos.line;
|
||||
int convertedCenterLine = myConvertor.execute(masterCenterLine);
|
||||
|
||||
int correction = (masterVerticalScrollOffset + middleY) % myMaster.getLineHeight();
|
||||
Point point = mySlave.logicalPositionToXY(new LogicalPosition(scrollToLine, masterPos.column));
|
||||
int offset = point.y - middleY + correction;
|
||||
Point point = mySlave.logicalPositionToXY(new LogicalPosition(convertedCenterLine, masterPos.column));
|
||||
int correction = (viewRect.y + middleY) % myMaster.getLineHeight();
|
||||
offset = point.y - middleY + correction;
|
||||
}
|
||||
else {
|
||||
double progress = myAnchor.masterStartOffset == myAnchor.masterEndOffset || viewRect.y == myAnchor.masterEndOffset ? 1 :
|
||||
((double)(viewRect.y - myAnchor.masterStartOffset)) / (myAnchor.masterEndOffset - myAnchor.masterStartOffset);
|
||||
|
||||
offset = myAnchor.slaveStartOffset + (int)((myAnchor.slaveEndOffset - myAnchor.slaveStartOffset) * progress);
|
||||
}
|
||||
|
||||
int deltaHeaderOffset = getHeaderOffset(mySlave) - getHeaderOffset(myMaster);
|
||||
doScrollVertically(mySlave.getScrollingModel(), offset + deltaHeaderOffset);
|
||||
doScrollVertically(mySlave, offset + deltaHeaderOffset, animated);
|
||||
}
|
||||
|
||||
private void syncHorizontalScroll(@NotNull Rectangle newRectangle, @NotNull Rectangle oldRectangle) {
|
||||
if (newRectangle.x == oldRectangle.x) return;
|
||||
|
||||
int offset = newRectangle.x;
|
||||
|
||||
doScrollHorizontally(mySlave.getScrollingModel(), offset);
|
||||
private void syncHorizontalScroll(boolean animated) {
|
||||
int offset = myMaster.getScrollingModel().getVisibleArea().x;
|
||||
doScrollHorizontally(mySlave, offset, animated);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doScrollVertically(@NotNull ScrollingModel model, int offset) {
|
||||
model.disableAnimation();
|
||||
try {
|
||||
model.scrollVertically(offset);
|
||||
}
|
||||
finally {
|
||||
model.enableAnimation();
|
||||
}
|
||||
private static void doScrollVertically(@NotNull Editor editor, int offset, boolean animated) {
|
||||
ScrollingModel model = editor.getScrollingModel();
|
||||
if (!animated) model.disableAnimation();
|
||||
model.scrollVertically(offset);
|
||||
if (!animated) model.enableAnimation();
|
||||
}
|
||||
|
||||
private static void doScrollHorizontally(@NotNull ScrollingModel model, int offset) {
|
||||
model.disableAnimation();
|
||||
try {
|
||||
model.scrollHorizontally(offset);
|
||||
}
|
||||
finally {
|
||||
model.enableAnimation();
|
||||
}
|
||||
private static void doScrollHorizontally(@NotNull Editor editor, int offset, boolean animated) {
|
||||
ScrollingModel model = editor.getScrollingModel();
|
||||
if (!animated) model.disableAnimation();
|
||||
model.scrollHorizontally(offset);
|
||||
if (!animated) model.enableAnimation();
|
||||
}
|
||||
|
||||
private static int getHeaderOffset(@NotNull final Editor editor) {
|
||||
final JComponent header = editor.getHeaderComponent();
|
||||
return header == null ? 0 : header.getHeight();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static IntPair getTargetOffsets(@NotNull Editor editor1, @NotNull Editor editor2,
|
||||
int startLine1, int endLine1, int startLine2, int endLine2) {
|
||||
int topOffset1 = editor1.logicalPositionToXY(new LogicalPosition(startLine1, 0)).y;
|
||||
int bottomOffset1 = editor1.logicalPositionToXY(new LogicalPosition(endLine1 + 1, 0)).y;
|
||||
int topOffset2 = editor2.logicalPositionToXY(new LogicalPosition(startLine2, 0)).y;
|
||||
int bottomOffset2 = editor2.logicalPositionToXY(new LogicalPosition(endLine2 + 1, 0)).y;
|
||||
|
||||
int rangeHeight1 = bottomOffset1 - topOffset1;
|
||||
int rangeHeight2 = bottomOffset2 - topOffset2;
|
||||
|
||||
int gapLines1 = 2 * editor1.getLineHeight();
|
||||
int gapLines2 = 2 * editor2.getLineHeight();
|
||||
|
||||
int editorHeight1 = editor1.getScrollingModel().getVisibleArea().height;
|
||||
int editorHeight2 = editor2.getScrollingModel().getVisibleArea().height;
|
||||
|
||||
int maximumOffset1 = ((EditorEx)editor1).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeight1;
|
||||
int maximumOffset2 = ((EditorEx)editor1).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeight2;
|
||||
|
||||
// 'shift' here - distance between editor's top and first line of range
|
||||
|
||||
// make whole range visible. If possible, locate it at 'center' (1/3 of height)
|
||||
// If can't show whole range - show as much as we can
|
||||
boolean canShow1 = 2 * gapLines1 + rangeHeight1 <= editorHeight1;
|
||||
boolean canShow2 = 2 * gapLines2 + rangeHeight2 <= editorHeight2;
|
||||
|
||||
int topShift1 = canShow1 ? Math.min(editorHeight1 - gapLines1 - rangeHeight1, editorHeight1 / 3) : gapLines1;
|
||||
int topShift2 = canShow2 ? Math.min(editorHeight2 - gapLines2 - rangeHeight2, editorHeight2 / 3) : gapLines2;
|
||||
|
||||
int topShift = Math.min(topShift1, topShift2);
|
||||
|
||||
// check if we're at the top of file
|
||||
topShift = Math.min(topShift, Math.min(topOffset1, topOffset2));
|
||||
|
||||
int offset1 = topOffset1 - topShift;
|
||||
int offset2 = topOffset2 - topShift;
|
||||
if (maximumOffset1 > offset1 && maximumOffset2 > offset2) return new IntPair(offset1, offset2);
|
||||
|
||||
// One of the ranges is at end of file - we can't scroll where we want to.
|
||||
topShift = Math.min(topOffset1 - maximumOffset1, topOffset2 - maximumOffset2);
|
||||
|
||||
// Try to show as much of range as we can (even if it breaks alignment)
|
||||
offset1 = topOffset1 - topShift + Math.max(topShift + rangeHeight1 + gapLines1 - editorHeight1, 0);
|
||||
offset2 = topOffset2 - topShift + Math.max(topShift + rangeHeight2 + gapLines2 - editorHeight2, 0);
|
||||
|
||||
// always show top of the range
|
||||
offset1 = Math.min(offset1, topOffset1 - gapLines1);
|
||||
offset2 = Math.min(offset2, topOffset2 - gapLines2);
|
||||
|
||||
return new IntPair(offset1, offset2);
|
||||
}
|
||||
|
||||
private static class Anchor {
|
||||
public final int masterStartOffset;
|
||||
public final int masterEndOffset;
|
||||
public final int slaveStartOffset;
|
||||
public final int slaveEndOffset;
|
||||
|
||||
public Anchor(int masterStartOffset, int masterEndOffset, int slaveStartOffset, int slaveEndOffset) {
|
||||
this.masterStartOffset = masterStartOffset;
|
||||
this.masterEndOffset = masterEndOffset;
|
||||
this.slaveStartOffset = slaveStartOffset;
|
||||
this.slaveEndOffset = slaveEndOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -82,7 +82,7 @@ public abstract class TwosideTextDiffViewer extends TextDiffViewerBase {
|
||||
|
||||
@NotNull private final MyScrollToLineHelper myScrollToLineHelper = new MyScrollToLineHelper();
|
||||
|
||||
@Nullable private TwosideSyncScrollSupport mySyncScrollListener;
|
||||
@Nullable protected TwosideSyncScrollSupport mySyncScrollSupport;
|
||||
|
||||
@NotNull private Side myCurrentSide;
|
||||
|
||||
@@ -230,7 +230,7 @@ public abstract class TwosideTextDiffViewer extends TextDiffViewerBase {
|
||||
if (myEditor1 != null && myEditor2 != null) {
|
||||
SyncScrollSupport.SyncScrollable scrollable = getSyncScrollable();
|
||||
if (scrollable != null) {
|
||||
mySyncScrollListener = new TwosideSyncScrollSupport(myEditor1, myEditor2, scrollable);
|
||||
mySyncScrollSupport = new TwosideSyncScrollSupport(myEditor1, myEditor2, scrollable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,15 +248,15 @@ public abstract class TwosideTextDiffViewer extends TextDiffViewerBase {
|
||||
myEditor2.getScrollingModel().removeVisibleAreaListener(myVisibleAreaListener);
|
||||
}
|
||||
if (myEditor1 != null && myEditor2 != null) {
|
||||
if (mySyncScrollListener != null) {
|
||||
mySyncScrollListener = null;
|
||||
if (mySyncScrollSupport != null) {
|
||||
mySyncScrollSupport = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void disableSyncScrollSupport(boolean disable) {
|
||||
if (mySyncScrollListener != null) {
|
||||
mySyncScrollListener.myDuringSyncScroll = disable;
|
||||
if (mySyncScrollSupport != null) {
|
||||
mySyncScrollSupport.myDuringSyncScroll = disable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,8 +325,8 @@ public abstract class TwosideTextDiffViewer extends TextDiffViewerBase {
|
||||
@CalledInAwt
|
||||
@NotNull
|
||||
protected LogicalPosition transferPosition(@NotNull Side baseSide, @NotNull LogicalPosition position) {
|
||||
if (mySyncScrollListener == null) return position;
|
||||
int line = mySyncScrollListener.getScrollable().transfer(baseSide, position.line);
|
||||
if (mySyncScrollSupport == null) return position;
|
||||
int line = mySyncScrollSupport.getScrollable().transfer(baseSide, position.line);
|
||||
return new LogicalPosition(line, position.column);
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ public abstract class TwosideTextDiffViewer extends TextDiffViewerBase {
|
||||
private class MyVisibleAreaListener implements VisibleAreaListener {
|
||||
@Override
|
||||
public void visibleAreaChanged(VisibleAreaEvent e) {
|
||||
if (mySyncScrollListener != null) mySyncScrollListener.visibleAreaChanged(e);
|
||||
if (mySyncScrollSupport != null) mySyncScrollSupport.visibleAreaChanged(e);
|
||||
if (Registry.is("diff.divider.repainting.fix")) {
|
||||
myContentPanel.repaint();
|
||||
}
|
||||
|
||||
@@ -191,6 +191,12 @@ public class DiffUtil {
|
||||
// Scrolling
|
||||
//
|
||||
|
||||
public static void moveCaret(@Nullable final Editor editor, int line) {
|
||||
if (editor == null) return;
|
||||
editor.getCaretModel().removeSecondaryCarets();
|
||||
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(line, 0));
|
||||
}
|
||||
|
||||
public static void scrollEditor(@Nullable final Editor editor, int line, boolean animated) {
|
||||
scrollEditor(editor, line, 0, animated);
|
||||
}
|
||||
@@ -203,11 +209,15 @@ public class DiffUtil {
|
||||
}
|
||||
|
||||
public static void scrollToPoint(@Nullable Editor editor, @NotNull Point point) {
|
||||
scrollToPoint(editor, point, false);
|
||||
}
|
||||
|
||||
public static void scrollToPoint(@Nullable Editor editor, @NotNull Point point, boolean animated) {
|
||||
if (editor == null) return;
|
||||
editor.getScrollingModel().disableAnimation();
|
||||
if (!animated) editor.getScrollingModel().disableAnimation();
|
||||
editor.getScrollingModel().scrollHorizontally(point.x);
|
||||
editor.getScrollingModel().scrollVertically(point.y);
|
||||
editor.getScrollingModel().enableAnimation();
|
||||
if (!animated) editor.getScrollingModel().enableAnimation();
|
||||
}
|
||||
|
||||
public static void scrollToCaret(@Nullable Editor editor, boolean animated) {
|
||||
|
||||
Reference in New Issue
Block a user