Skip to content
Merged
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 @@ -871,14 +871,21 @@ private static List<UnifiedDiff> computeDiffs(IDocument leftDocument, IDocument
case RangeDifference.NOCHANGE:
break;
case RangeDifference.CHANGE:
var diff = new UnifiedDiff(leftDocument, leftStart, leftEnd, leftDiffSource, rightDocument,
rightStart, rightEnd, rightDiffSource, unifiedDiffs, mode);
unifiedDiffs.add(diff);

// line based fine granular diff via DocumentMerger#simpleTokenDiff
ITokenComparator l = createTokenComparator(leftDiffSource, tokenComparatorFactory);
ITokenComparator r = createTokenComparator(rightDiffSource, tokenComparatorFactory);
RangeDifference[] detailedDiffs = RangeDifferencer.findRanges((IRangeComparator) null, l, r);
// The token comparator defines what counts as a real difference. When it
// reports none, the region is treated as equal in every mode: no annotation
// is shown, and REPLACE_MODE leaves the text as it is rather than rewriting
// it to a form the comparator considers identical.
if (!hasDetailedChanges(detailedDiffs)) {
break;
}
var diff = new UnifiedDiff(leftDocument, leftStart, leftEnd, leftDiffSource, rightDocument,
rightStart, rightEnd, rightDiffSource, unifiedDiffs, mode);
unifiedDiffs.add(diff);

for (RangeDifference detailedDiff : detailedDiffs) {
if (detailedDiff.kind() == RangeDifference.NOCHANGE) {
continue;
Expand Down Expand Up @@ -918,6 +925,15 @@ private static List<UnifiedDiff> computeDiffs(IDocument leftDocument, IDocument
return unifiedDiffs;
}

private static boolean hasDetailedChanges(RangeDifference[] detailedDiffs) {
for (RangeDifference detailedDiff : detailedDiffs) {
if (detailedDiff.kind() != RangeDifference.NOCHANGE) {
return true;
}
}
return false;
}

public static void error(Exception e) {
Platform.getLog(UnifiedDiffManager.class).error(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.util.Iterator;
import java.util.List;

import org.eclipse.compare.contentmergeviewer.ITokenComparator;
import org.eclipse.compare.rangedifferencer.IRangeComparator;
import org.eclipse.compare.unifieddiff.UnifiedDiff;
import org.eclipse.compare.unifieddiff.UnifiedDiffMode;
import org.eclipse.compare.unifieddiff.internal.UnifiedDiffManager;
Expand Down Expand Up @@ -255,6 +257,41 @@ public void testReplaceModeAnnotatesTheAppliedText() throws BadLocationException
"the annotation must cover the text that was inserted into the document");
}

/**
* The token comparator, not the mode, decides what counts as a difference. A
* case-insensitive comparator reports no difference between "line one" and
* "LINE ONE", so REPLACE_MODE must leave the document untouched rather than
* rewrite it to a form the comparator already considers identical.
*/
@Test
public void testReplaceModeKeepsDocumentWhenTokenComparatorTreatsChangeAsEqual() {
setEditorContent("line one\n");

assertTrue(UnifiedDiff.create(editor, "LINE ONE\n", UnifiedDiffMode.REPLACE_MODE).ignoreWhiteSpace(false)
.tokenComparatorFactory(CaseInsensitiveTokenComparator::new).open().isOK());

assertTrue(UnifiedDiffManager.get(viewer()).isEmpty(),
"a change the token comparator treats as equal must not be applied, even in REPLACE_MODE");
assertEquals("line one\n", document().get(),
"REPLACE_MODE must leave the document untouched when the comparator sees no real difference");
}

/**
* The counterpart to {@link #testReplaceModeKeepsDocumentWhenTokenComparatorTreatsChangeAsEqual()}:
* with the default comparator a case change is a real difference, so REPLACE_MODE
* applies it and the document becomes the compared source.
*/
@Test
public void testReplaceModeAppliesCaseChangeWithDefaultComparator() {
setEditorContent("line one\n");

assertTrue(UnifiedDiff.create(editor, "LINE ONE\n", UnifiedDiffMode.REPLACE_MODE).ignoreWhiteSpace(false).open()
.isOK());

assertEquals("LINE ONE\n", document().get(),
"with the default comparator a case change is a real difference and must be applied");
}

// ---------------------------------------------------- non modifying modes

@Test
Expand Down Expand Up @@ -334,6 +371,17 @@ public void testWhitespaceOnlyChangeIsReportedWhenNotIgnored() {
"with ignoreWhiteSpace(false) the changed indentation is a diff");
}

@Test
public void testTokenComparatorCanIgnoreALineChange() {
setEditorContent("line one\n");

assertTrue(UnifiedDiff.create(editor, "LINE ONE\n", UnifiedDiffMode.OVERLAY_MODE)
.ignoreWhiteSpace(false).tokenComparatorFactory(CaseInsensitiveTokenComparator::new).open().isOK());

assertTrue(UnifiedDiffManager.get(viewer()).isEmpty(),
"a change ignored by the token comparator must not create a parent diff");
}

/**
* A diff at the very end of the document is shown as a code mining. Only a line
* header mining reserves its height in the text widget, so an emptied file must
Expand Down Expand Up @@ -433,6 +481,39 @@ private void assertReplaceModeYields(String left, String right) {
assertEquals(right, document().get(), "REPLACE_MODE must transform the document into the compared source");
}

private static final class CaseInsensitiveTokenComparator implements ITokenComparator {
private final String text;

CaseInsensitiveTokenComparator(String text) {
this.text = text;
}

@Override
public int getRangeCount() {
return 1;
}

@Override
public int getTokenStart(int index) {
return index == 0 ? 0 : text.length();
}

@Override
public int getTokenLength(int index) {
return index == 0 ? text.length() : 0;
}

@Override
public boolean rangesEqual(int thisIndex, IRangeComparator other, int otherIndex) {
return other instanceof CaseInsensitiveTokenComparator comparator && text.equalsIgnoreCase(comparator.text);
}

@Override
public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) {
return false;
}
}

private ITextViewer viewer() {
ITextViewer viewer = editor.getAdapter(ITextViewer.class);
assertNotNull(viewer, "editor must adapt to ITextViewer");
Expand Down
Loading