Skip to content
Open
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 @@ -13,16 +13,20 @@
*******************************************************************************/
package org.eclipse.compare.unifieddiff;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

import org.eclipse.compare.contentmergeviewer.IIgnoreWhitespaceContributor;
import org.eclipse.compare.contentmergeviewer.ITokenComparator;
import org.eclipse.compare.unifieddiff.internal.ToolbarActionPresentations;
import org.eclipse.compare.unifieddiff.internal.UnifiedDiffManager;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.texteditor.ITextEditor;

Expand Down Expand Up @@ -51,6 +55,41 @@ public static Builder create(ITextEditor editor, String source, UnifiedDiffMode
return new Builder(editor, source, mode);
}

/**
* Identifies a built-in action of a unified diff toolbar, so that its text or
* image can be replaced. Which actions are shown depends on the
* {@link UnifiedDiffMode}; unused constants are simply ignored.
*
* @see Builder#toolbarActionText(ToolbarAction, String)
* @see Builder#toolbarActionImage(ToolbarAction, ImageDescriptor)
*/
public enum ToolbarAction {
/** Applies every diff, in the toolbar shown for all diffs. */
ACCEPT_ALL,
/** Discards every diff, in the toolbar shown for all diffs. */
HIDE_ALL,
/** Reverts every diff, in the toolbar shown for all diffs. */
REVERT_ALL,
/** Keeps every diff, in the toolbar shown for all diffs. */
KEEP_ALL,
/** Undoes every diff, in the toolbar shown for all diffs. */
UNDO_ALL,
/** Reveals the previous diff, in the toolbar shown for all diffs. */
PREVIOUS,
/** Reveals the next diff, in the toolbar shown for all diffs. */
NEXT,
/** Applies the hovered diff, in the toolbar shown for a single diff. */
ACCEPT,
/** Discards the hovered diff, in the toolbar shown for a single diff. */
HIDE,
/** Reverts the hovered diff, in the toolbar shown for a single diff. */
REVERT,
/** Keeps the hovered diff, in the toolbar shown for a single diff. */
KEEP,
/** Undoes the hovered diff, in the toolbar shown for a single diff. */
UNDO
}

public static final class Builder {
// Required parameters
private final ITextEditor editor;
Expand All @@ -63,6 +102,8 @@ public static final class Builder {
private TokenComparatorFactory tokenComparatorFactory;
private IgnoreWhitespaceContributorFactory ignoreWhitespaceContributorFactory;
private int foldContextLines = -1;
private final Map<ToolbarAction, String> toolbarActionTexts = new EnumMap<>(ToolbarAction.class);
private final Map<ToolbarAction, ImageDescriptor> toolbarActionImages = new EnumMap<>(ToolbarAction.class);

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

/**
* Overrides the text, including the tooltip text, for an action in a unified
* diff toolbar.
*
* @param action the action whose text is overridden
* @param text the replacement text
* @return this builder
*/
public Builder toolbarActionText(ToolbarAction action, String text) {
toolbarActionTexts.put(Objects.requireNonNull(action, "Action cannot be null"), //$NON-NLS-1$
Objects.requireNonNull(text, "Action text cannot be null")); //$NON-NLS-1$
return this;
}

/**
* Overrides the image for an action in a unified diff toolbar. Passing
* <code>null</code> removes the action's default image and displays its text
* instead.
*
* @param action the action whose image is overridden
* @param image the replacement image, or <code>null</code> to show text
* @return this builder
*/
public Builder toolbarActionImage(ToolbarAction action, ImageDescriptor image) {
toolbarActionImages.put(Objects.requireNonNull(action, "Action cannot be null"), image); //$NON-NLS-1$
return this;
}

/**
* Collapses unchanged regions between diffs, keeping the given number of
* context lines (at least one) around each change. A negative value disables
Expand All @@ -106,7 +175,8 @@ public Builder foldUnchanged(int contextLines) {

public IStatus open() {
return UnifiedDiffManager.open(editor, source, mode, additionalActions, tokenComparatorFactory,
ignoreWhitespaceContributorFactory, ignoreWhiteSpace, foldContextLines);
ignoreWhitespaceContributorFactory, ignoreWhiteSpace, foldContextLines,
new ToolbarActionPresentations(toolbarActionTexts, toolbarActionImages));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2026 SAP
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP - initial implementation
*******************************************************************************/
package org.eclipse.compare.unifieddiff.internal;

import java.util.EnumMap;
import java.util.Map;

import org.eclipse.compare.unifieddiff.UnifiedDiff.ToolbarAction;
import org.eclipse.jface.resource.ImageDescriptor;

/**
* The texts and images a caller supplied for the built-in toolbar actions.
* Absent entries fall back to the default of the action; an entry mapped to
* <code>null</code> is an override to "no image".
*/
public final class ToolbarActionPresentations {

/** No overrides, so every action keeps its default text and image. */
public static final ToolbarActionPresentations NONE = new ToolbarActionPresentations(Map.of(), Map.of());

private final Map<ToolbarAction, String> texts = new EnumMap<>(ToolbarAction.class);
private final Map<ToolbarAction, ImageDescriptor> images = new EnumMap<>(ToolbarAction.class);

public ToolbarActionPresentations(Map<ToolbarAction, String> texts, Map<ToolbarAction, ImageDescriptor> images) {
this.texts.putAll(texts);
this.images.putAll(images);
}

String text(ToolbarAction action, String defaultText) {
return texts.getOrDefault(action, defaultText);
}

ImageDescriptor image(ToolbarAction action, ImageDescriptor defaultImage) {
return images.containsKey(action) ? images.get(action) : defaultImage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.eclipse.compare.rangedifferencer.RangeDifferencer;
import org.eclipse.compare.unifieddiff.UnifiedDiff.IgnoreWhitespaceContributorFactory;
import org.eclipse.compare.unifieddiff.UnifiedDiff.TokenComparatorFactory;
import org.eclipse.compare.unifieddiff.UnifiedDiff.ToolbarAction;
import org.eclipse.compare.unifieddiff.UnifiedDiffMode;
import org.eclipse.compare.unifieddiff.internal.UnifiedDiffCodeMiningProvider.UnifiedDiffLineHeaderCodeMining;
import org.eclipse.core.resources.IFile;
Expand Down Expand Up @@ -123,9 +124,11 @@ public class UnifiedDiffManager {
private static final String DETAILED_DELETION_ANNO_TYPE = "org.eclipse.compare.unifieddiff.internal.detailedDeletion"; //$NON-NLS-1$
private static final String TOOLBAR_COMPOSITE_FOR_ONE_DIFF_KEY = "TOOLBAR_COMPOSITE_FOR_ONE_DIFF_KEY"; //$NON-NLS-1$
private static final String TOOLBAR_COMPOSITE_FOR_ALL_DIFFS_KEY = "TOOLBAR_COMPOSITE_FOR_ALL_DIFFS_KEY"; //$NON-NLS-1$
private static final String TOOLBAR_ACTION_PRESENTATIONS_KEY = "TOOLBAR_ACTION_PRESENTATIONS_KEY"; //$NON-NLS-1$
private static final Map<ITextViewer, List<UnifiedDiff>> diffsByViewer = new HashMap<>();

/**

* The user canceled the computation. The text editor is already open, so the
* caller must not fall back to the classic compare editor. Compared by identity,
* because a status code would clash with {@link Status#CANCEL_STATUS}, which
Expand All @@ -142,6 +145,16 @@ public static List<UnifiedDiff> get(ITextViewer viewer) {
return diffsByViewer.get(viewer);
}

private static void setToolbarActionPresentations(ITextViewer viewer, ToolbarActionPresentations presentations) {
viewer.getTextWidget().setData(TOOLBAR_ACTION_PRESENTATIONS_KEY, presentations);
}

private static ToolbarActionPresentations toolbarActionPresentations(ITextViewer viewer) {
Object presentations = viewer.getTextWidget().getData(TOOLBAR_ACTION_PRESENTATIONS_KEY);
return presentations instanceof ToolbarActionPresentations result ? result : ToolbarActionPresentations.NONE;
}


/**
* Returns the annotation model the diffs of the given editor live in, or
* <code>null</code> when it has none. The document provider of an editor on
Expand Down Expand Up @@ -174,6 +187,14 @@ public static IStatus open(ITextEditor editor, String source, UnifiedDiffMode mo
TokenComparatorFactory tokenComparatorFactory,
IgnoreWhitespaceContributorFactory ignoreWhitespaceContributorFactory, boolean ignoreWhiteSpace,
int foldContextLines) {
return open(editor, source, mode, additionalActions, tokenComparatorFactory, ignoreWhitespaceContributorFactory,
ignoreWhiteSpace, foldContextLines, ToolbarActionPresentations.NONE);
}

public static IStatus open(ITextEditor editor, String source, UnifiedDiffMode mode, List<Action> additionalActions,
TokenComparatorFactory tokenComparatorFactory,
IgnoreWhitespaceContributorFactory ignoreWhitespaceContributorFactory, boolean ignoreWhiteSpace,
int foldContextLines, ToolbarActionPresentations toolbarActionPresentations) {
ITextViewer viewer = editor.getAdapter(ITextViewer.class);
IDocument leftDocument = editor.getDocumentProvider().getDocument(editor.getEditorInput());
IAnnotationModel editorModel = annotationModelOf(editor);
Expand All @@ -183,7 +204,7 @@ public static IStatus open(ITextEditor editor, String source, UnifiedDiffMode mo
}
IFile file = editor.getEditorInput().getAdapter(IFile.class);
return open(viewer, leftDocument, model, file, source, mode, additionalActions, tokenComparatorFactory,
ignoreWhitespaceContributorFactory, ignoreWhiteSpace, foldContextLines);
ignoreWhitespaceContributorFactory, ignoreWhiteSpace, foldContextLines, toolbarActionPresentations);
}

/**
Expand All @@ -196,6 +217,17 @@ public static IStatus open(ITextViewer viewer, IDocument leftDocument, IAnnotati
TokenComparatorFactory tokenComparatorFactory,
IgnoreWhitespaceContributorFactory ignoreWhitespaceContributorFactory, boolean ignoreWhiteSpace,
int foldContextLines) {
return open(viewer, leftDocument, model, file, source, mode, additionalActions, tokenComparatorFactory,
ignoreWhitespaceContributorFactory, ignoreWhiteSpace, foldContextLines,
ToolbarActionPresentations.NONE);
}

public static IStatus open(ITextViewer viewer, IDocument leftDocument, IAnnotationModel model, IFile file,
String source, UnifiedDiffMode mode, List<Action> additionalActions,
TokenComparatorFactory tokenComparatorFactory,
IgnoreWhitespaceContributorFactory ignoreWhitespaceContributorFactory, boolean ignoreWhiteSpace,
int foldContextLines, ToolbarActionPresentations toolbarActionPresentations) {

if (viewer instanceof ProjectionViewer pv) {
pv.doOperation(ProjectionViewer.EXPAND_ALL);
removeFoldAnnotations(pv);
Expand Down Expand Up @@ -279,12 +311,14 @@ public static IStatus open(ITextViewer viewer, IDocument leftDocument, IAnnotati
}

UnifiedDiffManager.put(viewer, unifiedDiffs);
setToolbarActionPresentations(viewer, toolbarActionPresentations);
addPaintListener(viewer, model, mode);
addMouseMoveListener(viewer, model);
if (viewer instanceof ISourceViewerExtension5 ext) {
ext.updateCodeMinings();
}
drawToolBarForAllDiffs(viewer, model, additionalActions, mode);
drawToolBarForAllDiffs(viewer, model, additionalActions, mode, toolbarActionPresentations);

addUndoListener(viewer, leftDocument, model);
addAnnoModelChangeListener(viewer, model);

Expand Down Expand Up @@ -969,33 +1003,41 @@ public UnifiedDiff getUnifiedDiff() {
}

private static void drawToolBarForAllDiffs(ITextViewer tv, IAnnotationModel model, List<Action> additionalActions,
UnifiedDiffMode mode) {
UnifiedDiffMode mode, ToolbarActionPresentations presentations) {
StyledText tw = tv.getTextWidget();
var tm = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL | SWT.RIGHT);
if (UnifiedDiffMode.OVERLAY_MODE.equals(mode) || UnifiedDiffMode.OVERLAY_READ_ONLY_MODE.equals(mode)) {
List<UnifiedDiff> diffs = get(tv);
if (!isReadOnly(diffs)) {
var acceptAll = new AcceptAllRunnable(tv, model);
addToolbarAction(tm, acceptAll.getLabel(), AcceptAllRunnable.getImageDescriptor(), acceptAll);
addToolbarAction(tm, presentations.text(ToolbarAction.ACCEPT_ALL, acceptAll.getLabel()),
presentations.image(ToolbarAction.ACCEPT_ALL, AcceptAllRunnable.getImageDescriptor()), acceptAll);
}
var hideAll = new HideAllDiffsRunnable(tv, model);
addToolbarAction(tm, hideAll.getLabel(), hideAll.getImageDescriptor(), hideAll);
addToolbarAction(tm, presentations.text(ToolbarAction.HIDE_ALL, hideAll.getLabel()),
presentations.image(ToolbarAction.HIDE_ALL, hideAll.getImageDescriptor()), hideAll);
} else if (UnifiedDiffMode.REVERT_MODE.equals(mode)) {
var revertAll = new AcceptAllRunnable(tv, model);
addToolbarAction(tm, CompareMessages.UnifiedDiff_revert, AcceptAllRunnable.getUndoImageDescriptor(), revertAll);
addToolbarAction(tm, presentations.text(ToolbarAction.REVERT_ALL, CompareMessages.UnifiedDiff_revert),
presentations.image(ToolbarAction.REVERT_ALL, AcceptAllRunnable.getUndoImageDescriptor()), revertAll);
var hideAll = new HideAllDiffsRunnable(tv, model);
addToolbarAction(tm, hideAll.getLabel(), hideAll.getImageDescriptor(), hideAll);
addToolbarAction(tm, presentations.text(ToolbarAction.HIDE_ALL, hideAll.getLabel()),
presentations.image(ToolbarAction.HIDE_ALL, hideAll.getImageDescriptor()), hideAll);
} else {
var keepAll = new KeepAllRunnable(tv, model);
addToolbarAction(tm, keepAll.getLabel(), null, keepAll);
addToolbarAction(tm, presentations.text(ToolbarAction.KEEP_ALL, keepAll.getLabel()),
presentations.image(ToolbarAction.KEEP_ALL, null), keepAll);
var undoAll = new UndoAllRunnable(tv, model);
addToolbarAction(tm, undoAll.getLabel(), null, undoAll);
addToolbarAction(tm, presentations.text(ToolbarAction.UNDO_ALL, undoAll.getLabel()),
presentations.image(ToolbarAction.UNDO_ALL, null), undoAll);
}

var previous = new PreviousRunnable(tv, model, tm);
addToolbarAction(tm, previous.getLabel(), previous.getImageDescriptor(), previous);
addToolbarAction(tm, presentations.text(ToolbarAction.PREVIOUS, previous.getLabel()),
presentations.image(ToolbarAction.PREVIOUS, previous.getImageDescriptor()), previous);
var next = new NextRunnable(tv, model, tm);
addToolbarAction(tm, next.getLabel(), next.getImageDescriptor(), next);
addToolbarAction(tm, presentations.text(ToolbarAction.NEXT, next.getLabel()),
presentations.image(ToolbarAction.NEXT, next.getImageDescriptor()), next);
if (additionalActions != null) {
for (var additionalAction : additionalActions) {
addToolbarAction(tm, additionalAction);
Expand Down Expand Up @@ -1206,22 +1248,29 @@ private static void dismissCurrentDiff(ITextViewer tv, IAnnotationModel model) {
private static void drawToolbarForOneDiff(ITextViewer tv, IAnnotationModel model) {
List<UnifiedDiff> diffs = get(tv);
StyledText tw = tv.getTextWidget();
ToolbarActionPresentations presentations = toolbarActionPresentations(tv);
var tm = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL | SWT.RIGHT);
if (isOverlay(diffs)) {
if (!isReadOnly(diffs)) {
addToolbarAction(tm, CompareMessages.UnifiedDiff_accept, AcceptAllRunnable.getImageDescriptor(),
addToolbarAction(tm, presentations.text(ToolbarAction.ACCEPT, CompareMessages.UnifiedDiff_accept),
presentations.image(ToolbarAction.ACCEPT, AcceptAllRunnable.getImageDescriptor()),
() -> applyDiffRightStr(tv, model));
}
addToolbarAction(tm, CompareMessages.UnifiedDiff_hideDiff, HideAllDiffsRunnable.getHideDiffImageDescriptor(),
addToolbarAction(tm, presentations.text(ToolbarAction.HIDE, CompareMessages.UnifiedDiff_hideDiff),
presentations.image(ToolbarAction.HIDE, HideAllDiffsRunnable.getHideDiffImageDescriptor()),
() -> dismissCurrentDiff(tv, model));
} else if (isRevert(diffs)) {
addToolbarAction(tm, CompareMessages.UnifiedDiff_revert, AcceptAllRunnable.getUndoImageDescriptor(),
addToolbarAction(tm, presentations.text(ToolbarAction.REVERT, CompareMessages.UnifiedDiff_revert),
presentations.image(ToolbarAction.REVERT, AcceptAllRunnable.getUndoImageDescriptor()),
() -> applyDiffRightStr(tv, model));
addToolbarAction(tm, CompareMessages.UnifiedDiff_hideDiff, HideAllDiffsRunnable.getHideDiffImageDescriptor(),
addToolbarAction(tm, presentations.text(ToolbarAction.HIDE, CompareMessages.UnifiedDiff_hideDiff),
presentations.image(ToolbarAction.HIDE, HideAllDiffsRunnable.getHideDiffImageDescriptor()),
() -> dismissCurrentDiff(tv, model));
} else {
addToolbarAction(tm, CompareMessages.UnifiedDiff_keep, null, () -> dismissCurrentDiff(tv, model));
addToolbarAction(tm, CompareMessages.UnifiedDiff_undo, null, () -> {
addToolbarAction(tm, presentations.text(ToolbarAction.KEEP, CompareMessages.UnifiedDiff_keep),
presentations.image(ToolbarAction.KEEP, null), () -> dismissCurrentDiff(tv, model));
addToolbarAction(tm, presentations.text(ToolbarAction.UNDO, CompareMessages.UnifiedDiff_undo),
presentations.image(ToolbarAction.UNDO, null), () -> {
var fToolbarShellForOneDiff = getToolbarCompositeForOneDiff(tv.getTextWidget());
if (fToolbarShellForOneDiff == null) {
return;
Expand Down
Loading
Loading