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 @@ -43,6 +43,7 @@
import java.util.ResourceBundle;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareNavigator;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.ICompareNavigator;
Expand Down Expand Up @@ -4196,6 +4197,34 @@ public void run() {
new MergeSourceViewer[] { fLeft, fRight, fAncestor },
AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER);
fHandlerService.registerAction(toggleLineNumbersAction, ITextEditorActionDefinitionIds.LINENUMBER_TOGGLE);

createShowUnifiedDiffItem(tbm);
}

/**
* Adds the action that switches this side by side comparison over to the
* unified diff, the counterpart of the unified diff's action to open the
* comparison. Only added when the input can be shown as a unified diff at all.
*/
private void createShowUnifiedDiffItem(ToolBarManager tbm) {
if (!(getCompareConfiguration().getContainer() instanceof CompareEditorInput input)
|| !CompareUIPlugin.canShowAsUnifiedDiff(input)) {
return;
}
Action showUnifiedDiff = new Action() {
@Override
public void run() {
CompareUIPlugin.getDefault().switchToUnifiedDiff(input, getWorkbenchPage(input));
}
};
Utilities.initAction(showUnifiedDiff, getResourceBundle(), "action.ShowUnifiedDiff."); //$NON-NLS-1$
tbm.add(new Separator());
tbm.add(new ActionContributionItem(showUnifiedDiff));
}

private static IWorkbenchPage getWorkbenchPage(CompareEditorInput input) {
IWorkbenchPart part = input.getWorkbenchPart();
return part == null ? null : part.getSite().getPage();
}

private void configureCompareFilterActions(Object input, Object ancestor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@ Editor.FindReplace.description=Find/Replace

action.IgnoreWhiteSpace.label=&Ignore White Space
action.IgnoreWhiteSpace.tooltip=Ignore White Space Where Applicable

action.ShowUnifiedDiff.label=Show Unified Diff
action.ShowUnifiedDiff.tooltip=Show the comparison as a unified diff
action.ShowUnifiedDiff.image=unifieddiff_co.svg
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,41 @@ private UnifiedDiffSource prepareUnifiedDiff(CompareEditorInput input, IProgress
CompareUIPlugin.log(e);
return null;
}
if (!(input.getCompareResult() instanceof ICompareInput compareInput)) {
return unifiedDiffSourceOf(input);
}

/**
* Collects what the unified diff needs from an input that has already been run.
* Reads the side that supplies the diff, so it must not be called on the UI
* thread. Returns <code>null</code> if the input cannot be shown as a unified
* diff.
*/
private UnifiedDiffSource unifiedDiffSourceOf(CompareEditorInput input) {
UnifiedDiffCandidate candidate = unifiedDiffCandidateOf(input);
if (candidate == null) {
return null;
}
// The other side supplies the diff source and is read here rather than on the
// UI thread.
return new UnifiedDiffSource(candidate.compareInput(), candidate.editorInput(), candidate.element(),
candidate.mode(), getSourceOf(candidate.diffSource()));
}

/**
* Returns whether the result of the given input can be displayed as a unified
* diff. Answers from the structure of the compare result alone, without reading
* any content, so it is cheap enough for deciding whether to offer the switch.
*/
public static boolean canShowAsUnifiedDiff(CompareEditorInput input) {
return unifiedDiffCandidateOf(input) != null;
}

/**
* Picks the side the unified diff sits on, without reading any content.
* Returns <code>null</code> if neither side qualifies.
*/
private static UnifiedDiffCandidate unifiedDiffCandidateOf(CompareEditorInput input) {
if (input == null || !(input.getCompareResult() instanceof ICompareInput compareInput)) {
return null;
}
// A common ancestor (3-way input) is ignored; the unified diff renders a
Expand All @@ -869,19 +903,60 @@ private UnifiedDiffSource prepareUnifiedDiff(CompareEditorInput input, IProgress
return null;
}
// The overlay needs a workspace file to sit on; an editor opened on anything
// else, a revision for example, comes up empty. The other side supplies the
// diff source and is read here rather than on the UI thread.
// else, a revision for example, comes up empty.
if (leftEditorInput instanceof IFileEditorInput) {
return new UnifiedDiffSource(compareInput, leftEditorInput, left, UnifiedDiffMode.REVERT_MODE,
getSourceOf(rightSource));
return new UnifiedDiffCandidate(compareInput, leftEditorInput, left, UnifiedDiffMode.REVERT_MODE,
rightSource);
}
if (rightEditorInput instanceof IFileEditorInput) {
return new UnifiedDiffSource(compareInput, rightEditorInput, right, UnifiedDiffMode.OVERLAY_READ_ONLY_MODE,
getSourceOf(leftSource));
return new UnifiedDiffCandidate(compareInput, rightEditorInput, right,
UnifiedDiffMode.OVERLAY_READ_ONLY_MODE, leftSource);
}
return null;
}

/**
* Switches the compare editor showing the given input over to the unified diff.
* The compare editor is closed once the unified diff is up; when it cannot be
* shown, the compare editor is left as it is.
*/
public void switchToUnifiedDiff(final CompareEditorInput input, final IWorkbenchPage page) {
final IWorkbenchPage wpage = page != null ? page : getActivePage();
if (wpage == null || !canShowAsUnifiedDiff(input)) {
return;
}
Job job = new Job(NLS.bind(CompareMessages.UnifiedDiff_preparing, input.getTitle())) {
@Override
protected IStatus run(IProgressMonitor monitor) {
// The input already ran, so only the diff source has to be read here.
UnifiedDiffSource source = unifiedDiffSourceOf(input);
if (source == null || monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
Display.getDefault().asyncExec(() -> {
IEditorPart compareEditor = wpage.findEditor(input);
if (openUnifiedDiff(source, input, wpage, null, true) && compareEditor != null) {
// Prompts when the merge has unsaved changes.
wpage.closeEditor(compareEditor, true);
}
});
return Status.OK_STATUS;
}

@Override
public boolean belongsTo(Object family) {
return family == input || input.belongsTo(family);
}
};
job.setUser(true);
job.schedule();
}

/** The side the unified diff would sit on, before any content is read. */
private static record UnifiedDiffCandidate(ICompareInput compareInput, IEditorInput editorInput,
ITypedElement element, UnifiedDiffMode mode, IStreamContentAccessor diffSource) {
}

private static IEditorInput documentKeyOf(ITypedElement element) {
if (element == null) {
return null;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;

Expand All @@ -39,9 +41,11 @@
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.SharedDocumentAdapter;
import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
import org.eclipse.compare.internal.CompareEditor;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.unifieddiff.internal.UnifiedDiffManager;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.core.resources.IFile;
Expand All @@ -52,6 +56,7 @@
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.Annotation;
Expand Down Expand Up @@ -340,6 +345,78 @@ public void testAlreadyOpenEditorSurvivesTheFallback() throws Exception {
"the pre-opened editor and the compare editor must both be open"); //$NON-NLS-1$
}

/**
* The switch is only offered for a comparison the unified diff can actually
* display, so the toolbar of an input without a workspace file stays clean.
*/
@Test
public void testOnlyAQualifyingInputCanBeShownAsUnifiedDiff() throws Exception {
RecordingCompareEditorInput qualifying = openClassicInput();
assertTrue(CompareUIPlugin.canShowAsUnifiedDiff(qualifying),
"a workspace file comparison must offer the unified diff"); //$NON-NLS-1$

RecordingCompareEditorInput inMemory = new RecordingCompareEditorInput(
new InMemoryElement("left.txt", "alpha\nbravo\n"), //$NON-NLS-1$ //$NON-NLS-2$
new InMemoryElement("right.txt", "alpha\nBRAVO\n")); //$NON-NLS-1$ //$NON-NLS-2$
CompareUI.openCompareEditor(inMemory);
pumpUntil(() -> inMemory.getCompareResult() != null, "the in-memory input was not prepared"); //$NON-NLS-1$
assertFalse(CompareUIPlugin.canShowAsUnifiedDiff(inMemory),
"an input without a workspace file must not offer the unified diff"); //$NON-NLS-1$
}

/**
* Switching a side by side comparison over to the unified diff must leave the
* unified diff behind, not both editors.
*/
@Test
public void testSwitchToUnifiedDiffReplacesTheCompareEditor() throws Exception {
RecordingCompareEditorInput input = openClassicInput();
int prepareInputCountBeforeSwitch = input.prepareInputCount.get();

CompareUIPlugin.getDefault().switchToUnifiedDiff(input, activePage());
pumpUntil(() -> activePage().getActiveEditor() instanceof ITextEditor,
"the unified diff editor did not open"); //$NON-NLS-1$

ITextEditor textEditor = assertInstanceOf(ITextEditor.class, activePage().getActiveEditor());
IAnnotationModel model = textEditor.getDocumentProvider().getAnnotationModel(textEditor.getEditorInput());
assertNotNull(model, "the unified diff editor must have an annotation model"); //$NON-NLS-1$
pumpUntil(() -> hasUnifiedDiffAnnotation(model), "unified diff annotations did not appear"); //$NON-NLS-1$

assertFalse(hasCompareEditor(), "the compare editor must close when the unified diff takes over"); //$NON-NLS-1$
assertEquals(1, activePage().getEditorReferences().length,
"switching must leave exactly one editor open"); //$NON-NLS-1$
assertEquals(prepareInputCountBeforeSwitch, input.prepareInputCount.get(),
"switching must reuse the already prepared input"); //$NON-NLS-1$
}

/** A missing resource key would leave the toolbar button blank. */
@Test
public void testShowUnifiedDiffActionIsFullyDescribed() {
ResourceBundle bundle = ResourceBundle.getBundle("org.eclipse.compare.contentmergeviewer.TextMergeViewerResources", //$NON-NLS-1$
Locale.getDefault(), TextMergeViewer.class.getClassLoader());
Action action = new Action() {
// nothing to run, only the presentation is inspected
};
Utilities.initAction(action, bundle, "action.ShowUnifiedDiff."); //$NON-NLS-1$

assertEquals("Show Unified Diff", action.getText(), "the action needs a label"); //$NON-NLS-1$ //$NON-NLS-2$
assertNotNull(action.getToolTipText(), "the action needs a tooltip"); //$NON-NLS-1$
assertNotNull(action.getImageDescriptor(), "the action needs an icon"); //$NON-NLS-1$
}

/** Opens the qualifying input in the classic compare editor. */
private RecordingCompareEditorInput openClassicInput() throws CoreException {
store().setValue(ComparePreferencePage.UNIFIED_DIFF, false);
IFile left = createFile("left.txt", "alpha\nbravo\ncharlie\ndelta\n"); //$NON-NLS-1$ //$NON-NLS-2$
IFile right = createFile("right.txt", "alpha\nBRAVO\ncharlie\ndelta\n"); //$NON-NLS-1$ //$NON-NLS-2$
RecordingCompareEditorInput input = new RecordingCompareEditorInput(new WorkspaceFileElement(left),
new WorkspaceFileElement(right));
CompareUI.openCompareEditor(input);
pumpUntil(UnifiedDiffOpenTest::hasCompareEditor, "the compare editor did not open"); //$NON-NLS-1$
pumpUntil(() -> input.getCompareResult() != null, "the compare input was not prepared"); //$NON-NLS-1$
return input;
}

private static String defaultEditorIdFor(IFile file) {
IEditorDescriptor descriptor = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(file.getName());
return descriptor == null ? null : descriptor.getId();
Expand Down
Loading