Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ private CompareMessages() {
public static String UnifiedDiff_openTwoWayCompare_tooltip;
public static String UnifiedDiff_preparing;
public static String UnifiedDiff_computing;
public static String UnifiedDiff_expandUnchangedLine;
public static String UnifiedDiff_expandUnchangedLines;

static {
NLS.initializeMessages(BUNDLE_NAME, CompareMessages.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,5 @@ UnifiedDiff_revert=Revert
UnifiedDiff_openTwoWayCompare_tooltip=Open in 2-way Compare Editor
UnifiedDiff_preparing=Preparing Unified Diff for {0}
UnifiedDiff_computing=Computing Unified Diff
UnifiedDiff_expandUnchangedLine=Expand 1 unchanged line
UnifiedDiff_expandUnchangedLines=Expand {0} unchanged lines
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
Expand Down Expand Up @@ -124,6 +125,7 @@ private String loadPreviewContentFromFile(String key) {
public static final String REMOVED_LINES_REGEX= PREFIX + "RemovedLinesRegex"; //$NON-NLS-1$
public static final String SWAPPED = PREFIX + "Swapped"; //$NON-NLS-1$
public static final String UNIFIED_DIFF = PREFIX + "UnifiedDiff"; //$NON-NLS-1$
public static final String UNIFIED_DIFF_FOLD_UNCHANGED = PREFIX + "UnifiedDiffFoldUnchanged"; //$NON-NLS-1$


private IPropertyChangeListener fPreferenceChangeListener;
Expand Down Expand Up @@ -156,6 +158,7 @@ private String loadPreviewContentFromFile(String key) {
new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL),
new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, SWAPPED),
new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, UNIFIED_DIFF),
new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, UNIFIED_DIFF_FOLD_UNCHANGED),
};
private final List<FieldEditor> editors = new ArrayList<>();
private CTabItem fTextCompareTab;
Expand All @@ -180,6 +183,7 @@ public static void initDefaults(IPreferenceStore store) {
store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, ICompareUIConstants.PREF_VALUE_LOOP);
store.setDefault(SWAPPED, true);
store.setDefault(UNIFIED_DIFF, false);
store.setDefault(UNIFIED_DIFF_FOLD_UNCHANGED, true);
}

public ComparePreferencePage() {
Expand Down Expand Up @@ -289,7 +293,14 @@ private Control createGeneralPage(Composite parent) {
addCheckBox(composite, "ComparePreferencePage.structureCompare.label", OPEN_STRUCTURE_COMPARE, 0); //$NON-NLS-1$
addCheckBox(composite, "ComparePreferencePage.structureOutline.label", USE_OUTLINE_VIEW, 0); //$NON-NLS-1$
addCheckBox(composite, "ComparePreferencePage.ignoreWhitespace.label", IGNORE_WHITESPACE, 0); //$NON-NLS-1$
addCheckBox(composite, "ComparePreferencePage.unifiedDiff.label", UNIFIED_DIFF, 0); //$NON-NLS-1$
Group unifiedDiffGroup= new Group(composite, SWT.NONE);
unifiedDiffGroup.setText(Utilities.getString("ComparePreferencePage.unifiedDiffGroup.label")); //$NON-NLS-1$
unifiedDiffGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
unifiedDiffGroup.setLayout(new GridLayout(1, false));

addCheckBox(unifiedDiffGroup, "ComparePreferencePage.unifiedDiff.label", UNIFIED_DIFF, 0); //$NON-NLS-1$
addCheckBox(unifiedDiffGroup, "ComparePreferencePage.unifiedDiffFoldUnchanged.label", //$NON-NLS-1$
UNIFIED_DIFF_FOLD_UNCHANGED, 0);

// a spacer
new Label(composite, SWT.NONE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ private static final class PrefetchedElement {

public static final int NO_DIFFERENCE = 10000;

// Number of unchanged context lines kept around each change when the unified
// diff collapses unchanged regions.
private static final int UNIFIED_DIFF_CONTEXT_LINES = 3;

/**
* The plugin singleton.
*/
Expand Down Expand Up @@ -633,6 +637,13 @@ public void openCompareEditor(final CompareEditorInput input,
openClassicCompareEditor(input, page, editor, activate);
}

/** The context lines to keep, or a negative value when folding is turned off. */
private static int foldUnchangedContextLines() {
return getDefault().getPreferenceStore().getBoolean(ComparePreferencePage.UNIFIED_DIFF_FOLD_UNCHANGED)
? UNIFIED_DIFF_CONTEXT_LINES
: -1;
}

/** Opens the classic side by side compare editor on the given input. */
private void openClassicCompareEditor(final CompareEditorInput input, final IWorkbenchPage page,
final IReusableEditor editor, final boolean activate) {
Expand Down Expand Up @@ -736,6 +747,7 @@ private boolean openUnifiedDiff(UnifiedDiffSource source, CompareEditorInput inp
.tokenComparatorFactory(t -> mergerInput != null ? mergerInput.createTokenComparator(t) : null)
.ignoreWhiteSpace(Utilities.getBoolean(input.getCompareConfiguration(),
CompareConfiguration.IGNORE_WHITESPACE, false))
.foldUnchanged(foldUnchangedContextLines())
.open();
// The user canceled the diff, not the open: leave the text editor alone
// instead of falling back to the classic compare editor.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public static final class Builder {
private List<Action> additionalActions;
private TokenComparatorFactory tokenComparatorFactory;
private IgnoreWhitespaceContributorFactory ignoreWhitespaceContributorFactory;
private int foldContextLines = -1;

private Builder(ITextEditor editor, String source, UnifiedDiffMode mode) {
this.editor = Objects.requireNonNull(editor, "Editor cannot be null"); //$NON-NLS-1$
Expand Down Expand Up @@ -89,9 +90,23 @@ public Builder ignoreWhiteSpace(boolean value) {
return this;
}

/**
* Collapses unchanged regions between diffs, keeping the given number of
* context lines (at least one) around each change. A negative value disables
* folding.
* <p>
* The folding of the editor is reused, so this has no effect in an editor
* without folding support, such as the default text editor, or in an editor
* whose folding the user turned off.
*/
public Builder foldUnchanged(int contextLines) {
this.foldContextLines = contextLines;
return this;
}

public IStatus open() {
return UnifiedDiffManager.open(editor, source, mode, additionalActions, tokenComparatorFactory,
ignoreWhitespaceContributorFactory, ignoreWhiteSpace);
ignoreWhitespaceContributorFactory, ignoreWhiteSpace, foldContextLines);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.unifieddiff.UnifiedDiffMode;
import org.eclipse.compare.unifieddiff.internal.UnifiedDiffManager.UnifiedDiff;
import org.eclipse.core.runtime.IProgressMonitor;
Expand All @@ -55,6 +56,7 @@
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.inlined.LineFooterAnnotation;
import org.eclipse.jface.text.source.inlined.LineHeaderAnnotation;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
Expand Down Expand Up @@ -82,6 +84,7 @@ public class UnifiedDiffCodeMiningProvider extends AbstractCodeMiningProvider {

private Color deletionBackgroundColor;
private Color detailedDiffColor;
private Color foldSeparatorColor;
private boolean lastIsOverlay;

@Override
Expand All @@ -95,6 +98,10 @@ public void dispose() {
detailedDiffColor.dispose();
}
detailedDiffColor = null;
if (foldSeparatorColor != null && !foldSeparatorColor.isDisposed()) {
foldSeparatorColor.dispose();
}
foldSeparatorColor = null;
} finally {
super.dispose();
}
Expand Down Expand Up @@ -126,8 +133,12 @@ public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextVi
if (this.deletionBackgroundColor != null && !this.deletionBackgroundColor.isDisposed()) {
this.deletionBackgroundColor.dispose();
}
if (this.foldSeparatorColor != null && !this.foldSeparatorColor.isDisposed()) {
this.foldSeparatorColor.dispose();
}
this.detailedDiffColor = new Color(interpolate(deletionColor, background, 0.9));
this.deletionBackgroundColor = new Color(interpolate(deletionColor, background, 0.8));
this.foldSeparatorColor = new Color(separatorBackground(background));
lastIsOverlay = isOverlay;
}
if (viewer instanceof ISourceViewer sv && UnifiedDiffManager.get(viewer) != null) {
Expand Down Expand Up @@ -161,7 +172,13 @@ public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextVi
}
}
}
if (existingMinings.size() > 0) {
// Only the complete set may be reused. Collapsing a region can keep an
// annotation from being created, and reusing what is left would drop that
// diff for good, because nothing recomputes it from the diffs afterwards.
if (existingMinings.size() >= expectedMiningCount(diffs)) {
// the expander minings are recreated instead of reused so that they
// reflect the current expansion state of the folds
createFoldRegionCodeMinings(viewer, existingMinings);
return CompletableFuture.completedFuture(existingMinings);
}
}
Expand All @@ -170,9 +187,13 @@ public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextVi
// take an immutable snapshot so the async iteration cannot observe
// concurrent modifications when accept/hide actions mutate the live list
List<UnifiedDiff> diffsSnapshot = List.copyOf(diffs);
// created on the calling thread because it reads the projection annotation model
List<ICodeMining> foldMinings = new ArrayList<>();
createFoldRegionCodeMinings(viewer, foldMinings);
return CompletableFuture.supplyAsync(() -> {
List<ICodeMining> minings = new ArrayList<>();
createLineHeaderCodeMinings(diffsSnapshot, minings, viewer, tabWidth);
minings.addAll(foldMinings);
return minings;
});
}
Expand Down Expand Up @@ -217,6 +238,17 @@ private int getTabWidth(ITextViewer viewer) {
return tabWidth;
}

/** How many minings {@link #createLineHeaderCodeMinings} would create for the diffs. */
public static int expectedMiningCount(List<UnifiedDiff> diffs) {
int expected = 0;
for (UnifiedDiff diff : diffs) {
if (diff.mode.equals(UnifiedDiffMode.REPLACE_MODE) ? !diff.leftStr.isEmpty() : !diff.rightStr.isEmpty()) {
expected++;
}
}
return expected;
}

private void createLineHeaderCodeMinings(List<UnifiedDiff> diffs, List<ICodeMining> minings, ITextViewer tv,
int tabWidth) {
if (diffs == null) {
Expand Down Expand Up @@ -279,6 +311,78 @@ private static boolean startsLine(IDocument doc, int offset) throws BadLocationE
return doc.getLineOffset(doc.getLineOfOffset(offset)) == offset;
}

/**
* Creates one clickable expander mining per collapsed unchanged-region fold,
* shown as e.g. "Expand 42 unchanged lines" above the fold's caption line.
*/
private void createFoldRegionCodeMinings(ITextViewer viewer, List<ICodeMining> minings) {
IDocument doc = viewer.getDocument();
if (doc == null) {
return;
}
Map<Annotation, Position> folds = UnifiedDiffManager.getCollapsedFoldRegions(viewer);
for (Map.Entry<Annotation, Position> fold : folds.entrySet()) {
Position position = fold.getValue();
try {
int firstLine = doc.getLineOfOffset(position.getOffset());
int lastLine = position.getLength() > 0
? doc.getLineOfOffset(position.getOffset() + position.getLength() - 1)
: firstLine;
// the first line of the region stays visible as the fold's caption
int hiddenLines = lastLine - firstLine;
if (hiddenLines <= 0) {
continue;
}
minings.add(new FoldedRegionCodeMining(new Position(position.getOffset(), 1), this, viewer,
fold.getKey(), hiddenLines, this.foldSeparatorColor));
} catch (BadLocationException e) {
error(e);
}
}
}

/**
* A band that stands out from the surrounding text, so the collapsed region
* reads as a break between two hunks rather than as another line of the file.
*/
private static RGB separatorBackground(RGB background) {
boolean dark = background != null && (background.red + background.green + background.blue) / 3 < 128;
return interpolate(dark ? new RGB(255, 255, 255) : new RGB(0, 0, 0), background, 0.92);
}

static class FoldedRegionCodeMining extends LineHeaderCodeMining {

private final String expandLabel;
private final Color separatorColor;

public FoldedRegionCodeMining(Position position, ICodeMiningProvider provider, ITextViewer viewer,
Annotation foldAnnotation, int hiddenLines, Color separatorColor) throws BadLocationException {
super(position, provider, e -> UnifiedDiffManager.expandFoldRegion(viewer, foldAnnotation));
this.expandLabel = hiddenLines == 1 ? CompareMessages.UnifiedDiff_expandUnchangedLine
: NLS.bind(CompareMessages.UnifiedDiff_expandUnchangedLines, Integer.valueOf(hiddenLines));
this.separatorColor = separatorColor;
}

@Override
public String getLabel() {
return this.expandLabel;
}

@Override
public Point draw(GC gc, StyledText textWidget, Color color, int x, int y) {
if (this.separatorColor == null || this.separatorColor.isDisposed()) {
return super.draw(gc, textWidget, color, x, y);
}
gc.setBackground(this.separatorColor);
gc.setForeground(textWidget.getForeground());
gc.setFont(textWidget.getFont());
// a first run only to learn how tall the band has to be
Point size = super.draw(gc, textWidget, color, x, y);
gc.fillRectangle(0, y, textWidget.getBounds().width, size.y);
return super.draw(gc, textWidget, color, x, y);
}
}

static class UnifiedDiffFooterCodeMining extends DocumentFooterCodeMining {
private final String unifiedDiffLabel;
private final Color deletionBackgroundColor;
Expand Down
Loading
Loading