Introduce Unified Diff display mode to Compare editor#2560
Introduce Unified Diff display mode to Compare editor#2560tobiasmelcher wants to merge 1 commit into
Conversation
|
Looks amazing |
|
I very much like that feature. I would be in favor of integrating a usable state as early as possible and incrementally improve it, so that it becomes easy to test and so that you get feedback soon. You currently seem to introduce new API with the |
|
@tobias-melcher can you finish this one? Would be awesome to have this as option (hopefully the default 1-2 releases later). |
For me github shows by default a split view (what often is a bit better than the eclipse one but that's a different story) and one has to explicitly select "unified" - so I don't think one is the "default" over the other but its just two options one can switch between and depends on preference of the user. |
Sure, I will do my best. I think the most important topic is that we get rid of the reflection calls. I will concentrate on this topic in the next weeks. I understand the request to make this feature available as early as possible and mark it as experimental. I will do this. |
list Adds a new public API method `getMinings()` to `AbstractInlinedAnnotation` that provides read-only access to the list of `ICodeMining` instances associated with an inlined annotation. This API is needed to implement the unified diff feature via eclipse-platform/eclipse.platform#2560
2f247d4 to
d2dd3df
Compare
@HeikoKlare I marked the package as "provisional API" and added "EXPERIMENTAL" to the preference label with [900a124]. Would you prefer to add "internal" to package "org.eclipse.compare.unifieddiff" or is x-internal sufficient? |
list Adds a new public API method `getMinings()` to `AbstractInlinedAnnotation` that provides read-only access to the list of `ICodeMining` instances associated with an inlined annotation. This API is needed to implement the unified diff feature via eclipse-platform/eclipse.platform#2560
| private UnifiedDiff() { | ||
| } | ||
|
|
||
| public static enum UnifiedDiffMode { |
There was a problem hiding this comment.
Since this is API, I think using an enum here means that adding enum constants is a backwards incompatible change? Should this be updated to allow for adding new values without breaking compatibility?
There was a problem hiding this comment.
thanks a lot for the feedback @danthe1st . I have replaced the enum with a type-safe constant class [af1f8ef]. Do you have any further feedback?
I’d also like to better understand the compatibility concern: is adding an additional enum value really considered binary incompatible? If so, could you please point me to the relevant documentation that explains why enums are not allowed in Eclipse APIs?
There was a problem hiding this comment.
I don't know about that being anywhere in the Eclipse documentation but imagine this enum:
enum MyEnum {
A, B
}We can then use exhaustive switch expressions:
int oneBasedIndex(MyEnum e){
return switch(e) {
case A -> 1;
case B -> 2;
// case null -> 0; // optional
};
}Adding a new enum constant C will result in:
- A compilation failure at the
indexmethod
method - An
Errorat runtime whenoneBasedIndexis called withMyEnum.Cwithout recompiling/replacing the compilation unit containingMyEnum.C
I think there's also a way to reproduce this in JDKs before switch expressions using definite assignment rules (ensuring a variable is initialized).
There was a problem hiding this comment.
Thanks a lot for the clarification. Makes sense to me. You convinced me not to use enums in APIs.
There was a problem hiding this comment.
Actually, https://github.com/eclipse-platform/eclipse.platform/blob/master/docs/Evolving-Java-based-APIs-2.md#evolving-api-classes mentions
Add enum constant - Binary compatible (8)
and
(8) Client code can use the values() method to determine the ordinal positions of the enum constants. So although this is a binary compatible change, it may break contractual compatibility.
But I guess there's an argument to be made against that for the reasons I mentioned above.
list Adds a new public API method `getMinings()` to `AbstractInlinedAnnotation` that provides read-only access to the list of `ICodeMining` instances associated with an inlined annotation. This API is needed to implement the unified diff feature via eclipse-platform/eclipse.platform#2560
list Adds a new public API method `getMinings()` to `AbstractInlinedAnnotation` that provides read-only access to the list of `ICodeMining` instances associated with an inlined annotation. This API is needed to implement the unified diff feature via eclipse-platform/eclipse.platform#2560
I think marking the package as x-internal is sufficient. It's just important that OSGi will by default not allow you to access any content of that package. If I am not mistaken, we also have other packages that are marked into without having "internal" in their name (though it probably used to be common practice to have internal packages named like that some time ago). |
|
The old conversation was: use internal in package name if you do not plan to release it as API . Use x-internal if you plan to release it as API at some point in time but you are not yet ready for that |
|
@tobiasmelcher can I help with moving this forward? |
@vogella thanks a lot for asking and for offering to help. Sorry for the slow progress on my side. I haven’t had as much time as I expected over the last few weeks, so I wasn’t able to move this forward properly. I really appreciate your patience and support. |
Just as a remark, as Eclipse committer you can ask for a free pro Copilot license in your user profile, afterwards the Copilot CLI allows you to use Claude Opus 4.7 (or 4.6 in case the roll-out of 4.7 has not yet reached you) via the /model setting. Highly recommended in case you need assistance. |
Review is posted |
Thanks a lot. |
|
Hi @vogella , I added a dark.css file and registered it in the plugin.xml: The content in the dark.css seems not to be correct: Unfortunately, the light colors are used in the dark theme and not the registered ones in dark.css:
Do you know how to fix this? Thanks a lot, |
|
Looks like the css is not in the build.properties? |
|
This is is incorrect: The Pseudo class (:org-eclipse-ui-themes) is supposed to be the plug-in which contributes this, would be the ID of the bundle you have this in (the compare one), otherwise it gets overridden by the exsting org-eclipse-ui-editors:org-eclipse-ui-themes node or overrides it. I implemented this pseudo functionality to allow multiple plug-ins to contribute preference values for ui-editor preference node. |
|
Nice. So from your side this change is ready? |
I want to implement some unit tests, remove the test commands (TestUnidiffCommand, ...) and run some AI reviews. Ready is a big word; I would say we have a very first experimental version which could be used by interested users. When is the next dev close? Can we merge the first version in the beginning of the next release cycle? |
For the Eclipse Platform, M3 is next next Friday. RC2 is two weeks later (29th May) and usually the master branch will be opened for development of the next release shortly after. So beginning of June would be a good time for merging this. If I am not mistaken, the current proposal is a completely internal implementation with an opt-in feature, which in my opinion is perfect for merging in a rather early, experimental state and then improving incrementally while already having interested people testing it. |
8d25e19 to
7007282
Compare
be20078 to
e87f770
Compare
|
Hi everyone, I have just rebased this PR onto the latest master, so it is now up to date and ready for review. Please note that the Unified Diff feature introduced here is an early experimental version. The intention of merging it at this stage is to gather initial feedback from the community so users can try it out, report issues, and help guide its further development. Thanks a lot, |
HeikoKlare
left a comment
There was a problem hiding this comment.
+1 for merging this to allow easy testing of the new diff UI. Since the feature is marked experimental in the UI and technically internal, I do not see any risk in integrating it as is.
Note that I did not do a review of the code, but just found a typo by accident when look for how the feature is presented in the UI, which is why I have a single review comment.
Thank you again for working on this, @tobiasmelcher! Looking forward to having this available in the IDE and testing it.
| 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$ |
There was a problem hiding this comment.
| public static final String UNIFIED_DIFF = PREFIX + "UnitifedDiff"; //$NON-NLS-1$ | |
| public static final String UNIFIED_DIFF = PREFIX + "UnifiedDiff"; //$NON-NLS-1$ |
There was a problem hiding this comment.
thanks a lot Heiko, typo should be fixed with [95145e2]. I can remember that I fixed this typo some time ago, but somehow got lost then.
e87f770 to
95145e2
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces an experimental “Unified Diff” viewing mode for org.eclipse.compare, integrating a single-pane diff presentation (via annotations + code minings) into the text editor workflow and adding a preference to use it automatically for eligible 2‑way, read-only comparisons.
Changes:
- Added a new unified diff API surface (
org.eclipse.compare.unifieddiff) plus internal implementation for diff computation, annotations, toolbars, and code mining rendering. - Integrated unified diff opening into
CompareUIPlugin#openCompareEditor()behind a new Compare/Patch preference. - Added styling/resources (dark theme preference overrides, toolbar icons) and UI tests to validate annotation production + paint behavior.
Reviewed changes
Copilot reviewed 24 out of 26 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| team/tests/org.eclipse.team.tests.core/src/org/eclipse/team/tests/ui/UnifiedDiffManagerTest.java | Adds UI-level regression tests ensuring unified diff annotations are produced and can paint without logged errors. |
| team/tests/org.eclipse.team.tests.core/src/org/eclipse/team/tests/core/AllTeamUITests.java | Registers the new unified diff UI test in the UI test suite. |
| team/bundles/org.eclipse.compare/resources/css/dark.css | Defines dark-theme preference overrides for unified diff colors. |
| team/bundles/org.eclipse.compare/plugin.xml | Registers code mining provider + annotation types/specifications and dark-theme stylesheet. |
| team/bundles/org.eclipse.compare/plugin.properties | Adds label text for the new “Unified Diff” preference. |
| team/bundles/org.eclipse.compare/META-INF/MANIFEST.MF | Exports the new unified diff package (currently marked internal). |
| team/bundles/org.eclipse.compare/icons/full/elcl16/undo.svg | Adds toolbar icon for revert/undo-all behavior in unified diff. |
| team/bundles/org.eclipse.compare/icons/full/elcl16/remove_highlighting.svg | Adds toolbar icon for hiding diffs. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/UnifiedDiffMode.java | Introduces unified diff display mode constants. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/UnifiedDiff.java | Adds the public entry point/builder for opening unified diffs in editors. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/UnifiedDiffManager.java | Core unified diff engine: computes diffs, manages annotations, listeners, toolbars, and repaint coordination. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/UnifiedDiffCodeMiningProvider.java | Renders unified diff deletions/additions via line header/footer code minings and token-level highlighting. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/UndoAllRunnable.java | Implements “Undo All” action for replace-mode flows. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/PreviousRunnable.java | Implements previous-diff navigation action. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/NextRunnable.java | Implements next-diff navigation action. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/KeepAllRunnable.java | Implements “Keep All” for replace-mode flows. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/HideAllDiffsRunnable.java | Implements “Hide All Diffs” action and unified diff cleanup. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/unifieddiff/internal/AcceptAllRunnable.java | Implements “Accept/Revert All” action and applies changes into the document. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java | Exposes merger input to support unified diff token/whitespace contributors. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java | Adds icon constant for the “open 2‑way compare” action. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java | Adds unified diff preference handling and automatic unified diff opening in compare editor workflow. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java | Adds preference key/default and UI checkbox for unified diff mode. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties | Adds unified diff toolbar/action strings and error text. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java | Exposes the new message keys as fields. |
| team/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java | Adds adapter for IDocumentMergerInput and makes dispose path more null-safe. |
| team/bundles/org.eclipse.compare/build.properties | Ensures the new resources/ directory is included in the build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch (CoreException | IOException e) { | ||
| CompareUIPlugin.log(e); | ||
| } | ||
| return null; | ||
| } |
| var invisibleParent = new Shell(); | ||
| try { | ||
| invisibleParent.setVisible(false); | ||
| var viewer = input.findContentViewer(new NullViewer(getShell()), compareInput, invisibleParent); | ||
| if (viewer instanceof IAdaptable adaptable) { |
| try { | ||
| IWorkbenchPage wpage = page != null ? page : getActivePage(); | ||
| if (unifiedDiffInput.left instanceof IFileEditorInput) { | ||
| IEditorPart leftEditor = wpage.openEditor(unifiedDiffInput.left, | ||
| getEditorId(unifiedDiffInput.left, unifiedDiffInput.leftElement)); | ||
| if (leftEditor instanceof MultiPageEditorPart mpe) { |
| org.eclipse.ui.services, | ||
| org.eclipse.ui.texteditor" | ||
| org.eclipse.ui.texteditor", | ||
| org.eclipse.compare.unifieddiff;x-internal:=true |
| <extension point="org.eclipse.ui.editors.annotationTypes"> | ||
| <type | ||
| name="org.eclipse.compare.unifieddiff.internal.addition" | ||
| markerType="org.eclipse.core.resources.problemmarker" | ||
| markerSeverity="0"> | ||
| </type> | ||
| <type | ||
| name="org.eclipse.compare.unifieddiff.internal.deletion" | ||
| markerType="org.eclipse.core.resources.problemmarker" | ||
| markerSeverity="0"> | ||
| </type> | ||
| </extension> |
| // call validateEdit before modifying the leftDocument | ||
| IFile file = editor.getEditorInput().getAdapter(IFile.class); | ||
| if (file != null && !validateEdit(file)) { | ||
| return Status.CANCEL_STATUS; | ||
| } |
| RGB color; | ||
| if (mode.equals(UnifiedDiffMode.OVERLAY_MODE) || mode.equals(UnifiedDiffMode.OVERLAY_READ_ONLY_MODE)) { | ||
| color = JFaceResources.getColorRegistry().getRGB("DELETION_COLOR"); //$NON-NLS-1$ | ||
| } else { | ||
| color = JFaceResources.getColorRegistry().getRGB("ADDITION_COLOR"); //$NON-NLS-1$ | ||
| } | ||
| RGB background = UnifiedDiffCodeMiningProvider.getBackground(); | ||
| RGB interpolated = UnifiedDiffCodeMiningProvider.interpolate(color, background, 0.9); | ||
| this.additionBackgroundColor = new Color(interpolated); |
| int tabWidth = getTabWidth(viewer); | ||
| return CompletableFuture.supplyAsync(() -> { | ||
| List<ICodeMining> minings = new ArrayList<>(); | ||
| createLineHeaderCodeMinings(diffs, minings, viewer, tabWidth); | ||
| return minings; | ||
| }); |
| if (Display.getCurrent() != null && (this.deletionBackgroundColor == null || isOverlay != lastIsOverlay)) { | ||
| // check class ColorPalette | ||
| RGB background = getBackground(); | ||
| String colorName; | ||
| if (isOverlay) { | ||
| colorName = "ADDITION_COLOR"; //$NON-NLS-1$ | ||
| } else { | ||
| colorName = "DELETION_COLOR"; //$NON-NLS-1$ | ||
| } | ||
| RGB deletionColor = JFaceResources.getColorRegistry().getRGB(colorName); | ||
| this.detailedDiffColor = new Color(interpolate(deletionColor, background, 0.9)); | ||
| this.deletionBackgroundColor = new Color(interpolate(deletionColor, background, 0.8)); | ||
| lastIsOverlay = isOverlay; | ||
| } |
Compare This contribution introduces a new Unified Diff viewing mode for org.eclipse.compare that displays differences in a single editor pane, similar to git diff or GitHub's pull request view. When enabled, it replaces the traditional side-by-side 2-way compare editor for read-only comparisons, providing a more compact and familiar diff experience.
95145e2 to
2221516
Compare
|
Copilot findings fixed with [2221516] |
They has to. Egit releases support older Eclipse platforms. |
Of course they can; this is not a CompareEditor. EGit also shows a diff, not a single file comparison, with hunk headers. It would be nice if the new feature used the same colors as the EGit feature. EGit defines the dark mode colors at https://github.com/eclipse-egit/egit/blob/master/org.eclipse.egit.ui/css/e4-dark_egit_prefstyle.css , the default values (used in light mode) are at https://github.com/eclipse-egit/egit/blob/6f540c80c3b992787e6f7cb67d92d586385b6985/org.eclipse.egit.ui/plugin.xml#L880 and following. |






Add Unified Diff Display in Text Editor as Alternative to Classic 2-Way Compare
Implements eclipse-platform/eclipse.platform.ui#3771
Summary
This contribution introduces a new Unified Diff viewing mode for
org.eclipse.comparethat displays differences in a single editor pane, similar togit diffor GitHub's pull request view. When enabled, it replaces the traditional side-by-side 2-way compare editor for read-only comparisons, providing a more compact and familiar diff experience.This is a draft implementation to gather early feedback from the Eclipse community on:
What's Included
1. New Public API (
org.eclipse.compare.unifieddiff)Added a new public package with the
UnifiedDiffclass as the main entry point for programmatically opening unified diffs in text editors.Supported Modes:
API Example:
2. Preference Page Integration
Added a new checkbox in Preferences > General > Compare/Patch:
When enabled, eligible compare operations automatically use the unified diff viewer instead of the traditional side-by-side editor.
3. Automatic Compare Editor Integration
Modified
CompareUIPlugin#openCompareEditor()to detect when unified diff should be used:How to Test
Prerequisites
Steps
Expected Result:
Next Steps