Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4caa98f
Introduce Unified Diff display mode to Compare editor
tobias-melcher Mar 11, 2026
d2dd3df
remove reflection and use new API AbstractInlinedAnnotation.getMinings
tobias-melcher Apr 6, 2026
900a124
mark org.eclipse.compare.unifieddiff as provisional
tobias-melcher Apr 6, 2026
b1db4f4
remove accessing sourceviewer config via reflection to get tab width
tobias-melcher Apr 6, 2026
af1f8ef
Replace UnifiedDiffMode enum with type-safe constant class
tobias-melcher Apr 6, 2026
b8d4167
use SourceViewer.computeStyleRanges
tobias-melcher Apr 7, 2026
c58d4f6
enable syntax coloring after click inside code mining
tobias-melcher Apr 30, 2026
c581293
further improvements
tobias-melcher May 2, 2026
d0135c7
set dark colors for new unified diff editor annotations (via dark.css)
tobias-melcher May 3, 2026
5fc991b
replace focus listener with part listener
tobias-melcher May 4, 2026
40be321
refactor: replace Shell with Composite for toolbar management in unified
tobias-melcher May 6, 2026
4a47c61
cache draw operations in unified diff code mining for improved paint
tobias-melcher May 8, 2026
78715c4
fix tab measurement and font handling in unified diff code mining
tobias-melcher May 8, 2026
4fd9920
add detailed diff background highlighting to unified diff click overlay
tobias-melcher May 8, 2026
1d54bff
remove commands used for manual testing
tobias-melcher May 8, 2026
d963f1d
set missing line spacing in overlay text
tobias-melcher May 11, 2026
d037c54
refactor unified diff methods to return IStatus for better error
tobias-melcher May 11, 2026
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
5 changes: 3 additions & 2 deletions team/bundles/org.eclipse.compare/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.compare,
org.eclipse.compare.contentmergeviewer,
org.eclipse.compare.internal;x-friends:="org.eclipse.team.ui, org.eclipse.team.tests.core",
org.eclipse.compare.internal;x-friends:="org.eclipse.team.ui,org.eclipse.team.tests.core",
org.eclipse.compare.internal.merge;x-internal:=true,
org.eclipse.compare.internal.patch;x-friends:="org.eclipse.team.ui",
org.eclipse.compare.patch,
org.eclipse.compare.structuremergeviewer
org.eclipse.compare.structuremergeviewer,
org.eclipse.compare.unifieddiff;x-internal:=true
Require-Bundle: org.eclipse.ui;bundle-version="[3.206.0,4.0.0)",
org.eclipse.core.resources;bundle-version="[3.4.0,4.0.0)",
org.eclipse.jface.text;bundle-version="[3.8.0,4.0.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2063,9 +2063,15 @@ protected void handleDispose(DisposeEvent event) {
fBirdsEyeCanvas= null;
fSummaryHeader= null;

fAncestorContributor.unsetDocument(fAncestor);
fLeftContributor.unsetDocument(fLeft);
fRightContributor.unsetDocument(fRight);
if (fAncestorContributor != null) {
fAncestorContributor.unsetDocument(fAncestor);
}
if (fLeftContributor != null) {
fLeftContributor.unsetDocument(fLeft);
}
if (fRightContributor != null) {
fRightContributor.unsetDocument(fRight);
}

disconnect(fLeftContributor);
disconnect(fRightContributor);
Expand Down Expand Up @@ -5566,6 +5572,12 @@ public int getChangesCount() {
}
};
}
if (adapter == IDocumentMergerInput.class) {
if (fMerger == null) {
return null;
}
return (T) fMerger.getInput();
}
if (adapter == OutlineViewerCreator.class) {
if (fOutlineViewerCreator == null) {
fOutlineViewerCreator = new InternalOutlineViewerCreator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ private String loadPreviewContentFromFile(String key) {
public static final String ADDED_LINES_REGEX= PREFIX + "AddedLinesRegex"; //$NON-NLS-1$
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 + "UnitifedDiff"; //$NON-NLS-1$


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

public ComparePreferencePage() {
Expand Down Expand Up @@ -286,6 +289,7 @@ 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$

// a spacer
new Label(composite, SWT.NONE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
Expand All @@ -43,13 +45,18 @@
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.IResourceProvider;
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.IStreamMerger;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.internal.core.CompareSettings;
import org.eclipse.compare.internal.merge.DocumentMerger.IDocumentMergerInput;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.IStructureCreator;
import org.eclipse.compare.structuremergeviewer.SharedDocumentAdapterWrapper;
import org.eclipse.compare.structuremergeviewer.StructureDiffViewer;
import org.eclipse.compare.unifieddiff.UnifiedDiff;
import org.eclipse.compare.unifieddiff.UnifiedDiff.UnifiedDiffMode;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Adapters;
Expand All @@ -62,12 +69,14 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.preference.IPreferenceStore;
Expand All @@ -76,6 +85,7 @@
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
Expand All @@ -91,8 +101,10 @@
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.ITextEditor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

Expand Down Expand Up @@ -560,6 +572,10 @@ public void openCompareEditor(final CompareEditorInput input,
CompareConfiguration configuration = input.getCompareConfiguration();
if (configuration != null) {
IPreferenceStore ps= configuration.getPreferenceStore();
boolean unifiedDiffEnabled = ps.getBoolean(ComparePreferencePage.UNIFIED_DIFF);
if (unifiedDiffEnabled && openUnifiedDiffInEditor(input, page, editor, activate)) {
return;
}
if (ps != null) {
configuration.setProperty(
CompareConfiguration.USE_OUTLINE_VIEW,
Expand All @@ -575,9 +591,155 @@ public void openCompareEditor(final CompareEditorInput input,
}
}

private void openEditorInBackground(final CompareEditorInput input,
final IWorkbenchPage page, final IReusableEditor editor,
final boolean activate) {
private boolean openUnifiedDiffInEditor(final CompareEditorInput input, final IWorkbenchPage page,
IReusableEditor editor, boolean activate) {
var unifiedDiffInput = canShowInUnifiedDiff(input);
if (unifiedDiffInput!=null) {
try {
IWorkbenchPage wpage = page != null ? page : getActivePage();
IEditorPart rightEditor = wpage.openEditor(unifiedDiffInput.right,
getEditorId(unifiedDiffInput.right, unifiedDiffInput.rightElement));
if (rightEditor instanceof ITextEditor rightTextEditor) {
Action openTwoWayCompare = new Action("Open in 2-way Compare Editor", SWT.PUSH) {
@Override
public void run() {
if (input.canRunAsJob()) {
openEditorInBackground(input, page, editor, activate);
} else {
if (compareResultOK(input, null)) {
internalOpenEditor(input, page, editor, activate);
}
}
}
};
UnifiedDiff
.create(rightTextEditor, getSourceOf(unifiedDiffInput.leftAcessor()),
UnifiedDiffMode.OVERLAY_READ_ONLY_MODE)
.additionalActions(Arrays.asList(openTwoWayCompare))
.ignoreWhitespaceContributorFactory(
t -> unifiedDiffInput.documentMergerInput != null
? unifiedDiffInput.documentMergerInput.createIgnoreWhitespaceContributor(t)
: Optional.empty())
.tokenComparatorFactory(t -> unifiedDiffInput.documentMergerInput != null
? unifiedDiffInput.documentMergerInput.createTokenComparator(t)
: null)
.ignoreWhiteSpace(Utilities.getBoolean(input.getCompareConfiguration(),
CompareConfiguration.IGNORE_WHITESPACE, false))
.open();
}
return true;
} catch (PartInitException e) {
CompareUIPlugin.log(e);
}
}
return false;
}

private String getSourceOf(IStreamContentAccessor right) {
try {
if (right == null) {
return ""; //$NON-NLS-1$
}
String result = toString(right.getContents());
return result;
} catch (CoreException | IOException e) {
CompareUIPlugin.log(e);
}
return null;
}

private static String toString(InputStream inputStream) throws IOException {
if (inputStream == null) {
return ""; //$NON-NLS-1$
}
try (inputStream) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}

private static String getEditorId(IEditorInput editorInput, ITypedElement element) {
String fileName = editorInput.getName();
IEditorRegistry registry = PlatformUI.getWorkbench().getEditorRegistry();
IContentType type = getContentType(element);
IEditorDescriptor descriptor = registry.getDefaultEditor(fileName, type);
IDE.overrideDefaultEditorAssociation(editorInput, type, descriptor);
String id;
if (descriptor == null || descriptor.isOpenExternal()) {
id = "org.eclipse.ui.DefaultTextEditor"; //$NON-NLS-1$
} else {
id = descriptor.getId();
}
return id;
}

private static record LeftEditorInputAndRightStreamContentAccessor(IEditorInput left, ITypedElement leftElement,
IStreamContentAccessor leftAcessor, IEditorInput right, ITypedElement rightElement,
IStreamContentAccessor rightAcessor, IDocumentMergerInput documentMergerInput) {
}

private LeftEditorInputAndRightStreamContentAccessor canShowInUnifiedDiff(CompareEditorInput input) {
IDocumentMergerInput documentMergerInput = null;
try {
input.run(new NullProgressMonitor());
Object res = input.getCompareResult();
if (!(res instanceof ICompareInput compareInput)) {
return null;
}
ITypedElement ancestor = compareInput.getAncestor();
if (ancestor != null) {
return null;
}
// TODO (tm) do not support if one side is editable?
ITypedElement left = compareInput.getLeft();
if (left == null) {
return null;
}
ISharedDocumentAdapter leftSda = SharedDocumentAdapterWrapper.getAdapter(left);
if (leftSda == null) {
return null;
}
IEditorInput leftEditorInput = leftSda.getDocumentKey(left);
if (leftEditorInput == null) {
return null;
}
if (!(left instanceof IStreamContentAccessor leftSa)) {
return null;
}
ITypedElement right = compareInput.getRight();
if (right == null) {
return null;
}
ISharedDocumentAdapter rightSda = SharedDocumentAdapterWrapper.getAdapter(right);
if (rightSda == null) {
return null;
}
IEditorInput rightEditorInput = rightSda.getDocumentKey(right);
if (rightEditorInput == null) {
return null;
}
if (!(right instanceof IStreamContentAccessor rightSa)) {
return null;
}
var invisibleParent = new Shell();
try {
invisibleParent.setVisible(false);
var viewer = input.findContentViewer(new NullViewer(getShell()), compareInput, invisibleParent);
if (viewer instanceof IAdaptable adaptable) {
documentMergerInput = adaptable.getAdapter(IDocumentMergerInput.class);
}
} finally {
invisibleParent.dispose();
}
return new LeftEditorInputAndRightStreamContentAccessor(leftEditorInput, left, leftSa, rightEditorInput,
right, rightSa, documentMergerInput);
} catch (InvocationTargetException | InterruptedException e) {
CompareUIPlugin.log(e);
}
return null;
}

private void openEditorInBackground(final CompareEditorInput input, final IWorkbenchPage page,
final IReusableEditor editor, final boolean activate) {
internalOpenEditor(input, page, editor, activate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1403,4 +1403,7 @@ private Diff findPrev(char contributor, List<Diff> v, int start, int end, boolea
return null;
}

public IDocumentMergerInput getInput() {
return fInput;
}
}
Loading
Loading