IDEA-144761 diff: do not ignore whitespaces in string literals

This commit is contained in:
Aleksey Pivovarov
2017-02-02 18:21:33 +03:00
committed by Aleksey Pivovarov
parent b0f96c60a4
commit 74921ef65b
18 changed files with 665 additions and 42 deletions
@@ -0,0 +1,70 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.lang;
import com.intellij.lang.Language;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class JavaDiffIgnoredRangeProvider extends LangDiffIgnoredRangeProvider {
@NotNull
@Override
public String getDescription() {
return "Ignore imports and formatting";
}
@Override
protected boolean accepts(@NotNull Project project, @NotNull Language language) {
return JavaLanguage.INSTANCE.equals(language);
}
@NotNull
@Override
protected List<TextRange> computeIgnoredRanges(@NotNull Project project, @NotNull CharSequence text, @NotNull Language language) {
return ReadAction.compute(() -> {
List<TextRange> result = new ArrayList<>();
PsiFile psiFile = PsiFileFactory.getInstance(project).createFileFromText("", language, text);
psiFile.accept(new PsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (element.getTextLength() == 0) return;
if (isIgnored(element)) {
result.add(element.getTextRange());
}
else {
element.acceptChildren(this);
}
}
});
return result;
});
}
private static boolean isIgnored(@NotNull PsiElement element) {
if (element instanceof PsiWhiteSpace) return true;
if (element instanceof PsiImportList) return true;
return false;
}
}
@@ -23,14 +23,17 @@ import com.intellij.diff.util.MergeRange;
import com.intellij.diff.util.Range;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.diff.FilesTooBigForDiffException;
import com.intellij.util.text.CharSequenceSubSequence;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
@@ -39,6 +42,11 @@ import static java.util.Collections.singletonList;
public class ComparisonManagerImpl extends ComparisonManager {
private static final Logger LOG = Logger.getInstance(ComparisonManagerImpl.class);
@NotNull
public static ComparisonManagerImpl getInstanceImpl() {
return (ComparisonManagerImpl)getInstance();
}
@NotNull
@Override
public List<LineFragment> compareLines(@NotNull CharSequence text1,
@@ -433,6 +441,130 @@ public class ComparisonManagerImpl extends ComparisonManager {
return lines;
}
/**
* Compare two texts by-line and then compare changed fragments by-word
*/
@NotNull
public List<LineFragment> compareLinesWithIgnoredRanges(@NotNull CharSequence text1,
@NotNull CharSequence text2,
@NotNull List<TextRange> ignoredRanges1,
@NotNull List<TextRange> ignoredRanges2,
boolean innerFragments,
@NotNull ProgressIndicator indicator) throws DiffTooBigException {
BitSet ignored1 = collectIgnoredRanges(ignoredRanges1);
BitSet ignored2 = collectIgnoredRanges(ignoredRanges2);
List<Line> lines1 = getLines(text1);
List<Line> lines2 = getLines(text2);
List<CharSequence> lineTexts1 = ContainerUtil.map(lines1, line -> line.getNotIgnoredContent(ignored1));
List<CharSequence> lineTexts2 = ContainerUtil.map(lines2, line -> line.getNotIgnoredContent(ignored2));
FairDiffIterable iterable = ByLine.compare(lineTexts1, lineTexts2, ComparisonPolicy.DEFAULT, indicator);
List<LineFragment> lineFragments = convertIntoLineFragments(lines1, lines2, iterable);
if (innerFragments) {
lineFragments = createInnerFragments(lineFragments, text1, text2, ComparisonPolicy.DEFAULT, indicator);
}
return ContainerUtil.mapNotNull(lineFragments, fragment -> {
return trimIgnoredChanges(fragment, lines1, lines2, ignored1, ignored2);
});
}
@NotNull
private static BitSet collectIgnoredRanges(@NotNull List<TextRange> ignoredRanges) {
BitSet set = new BitSet();
for (TextRange range : ignoredRanges) {
set.set(range.getStartOffset(), range.getEndOffset());
}
return set;
}
@Nullable
private static LineFragment trimIgnoredChanges(@NotNull LineFragment fragment,
@NotNull List<Line> lines1,
@NotNull List<Line> lines2,
@NotNull BitSet ignored1,
@NotNull BitSet ignored2) {
// trim ignored lines
Range range = TrimUtil.trimExpandList(lines1, lines2,
fragment.getStartLine1(), fragment.getStartLine2(),
fragment.getEndLine1(), fragment.getEndLine2(),
(line1, line2) -> areIgnoredEqualLines(line1, line2, ignored1, ignored2),
line -> isIgnoredLine(line, ignored1),
line -> isIgnoredLine(line, ignored2));
int startLine1 = range.start1;
int startLine2 = range.start2;
int endLine1 = range.end1;
int endLine2 = range.end2;
if (startLine1 == endLine1 && startLine2 == endLine2) return null;
IntPair offsets1 = getOffsets(lines1, startLine1, endLine1);
IntPair offsets2 = getOffsets(lines2, startLine2, endLine2);
int startOffset1 = offsets1.val1;
int endOffset1 = offsets1.val2;
int startOffset2 = offsets2.val1;
int endOffset2 = offsets2.val2;
List<DiffFragment> newInner = null;
if (fragment.getInnerFragments() != null) {
int shift1 = startOffset1 - fragment.getStartOffset1();
int shift2 = startOffset2 - fragment.getStartOffset2();
int newCount1 = endOffset1 - startOffset1;
int newCount2 = endOffset2 - startOffset2;
newInner = ContainerUtil.mapNotNull(fragment.getInnerFragments(), it -> {
// update offsets, as some lines might have been ignored completely
int start1 = Math.max(it.getStartOffset1() - shift1, 0);
int start2 = Math.max(it.getStartOffset2() - shift2, 0);
int end1 = Math.max(Math.min(it.getEndOffset1() - shift1, newCount1), 0);
int end2 = Math.max(Math.min(it.getEndOffset2() - shift2, newCount2), 0);
// trim inner fragments
TextRange range1 = trimIgnoredRange(start1, end1, ignored1, startOffset1);
TextRange range2 = trimIgnoredRange(start2, end2, ignored2, startOffset2);
if (range1.isEmpty() && range2.isEmpty()) return null;
return new DiffFragmentImpl(range1.getStartOffset(), range1.getEndOffset(),
range2.getStartOffset(), range2.getEndOffset());
});
if (newInner.isEmpty()) return null;
}
return new LineFragmentImpl(startLine1, endLine1, startLine2, endLine2,
startOffset1, endOffset1, startOffset2, endOffset2,
newInner);
}
private static boolean isIgnoredLine(@NotNull Line line, @NotNull BitSet ignored) {
return trimIgnoredRange(line.getOffset1(), line.getOffset2(), ignored, 0).isEmpty();
}
private static boolean areIgnoredEqualLines(@NotNull Line line1, @NotNull Line line2,
@NotNull BitSet ignored1, @NotNull BitSet ignored2) {
int start1 = line1.getOffset1();
int end1 = line1.getOffset2();
int start2 = line2.getOffset1();
int end2 = line2.getOffset2();
Range range = TrimUtil.trimExpandText(line1.getOriginalText(), line2.getOriginalText(),
start1, start2, end1, end2,
ignored1, ignored2);
return range.isEmpty();
}
@NotNull
private static TextRange trimIgnoredRange(int start, int end, @NotNull BitSet ignored, int offset) {
IntPair intPair = TrimUtil.trim(offset + start, offset + end, ignored);
return new TextRange(intPair.val1 - offset, intPair.val2 - offset);
}
private static class Line {
@NotNull private final CharSequence myChars;
private final int myOffset1;
@@ -458,5 +590,20 @@ public class ComparisonManagerImpl extends ComparisonManager {
public CharSequence getContent() {
return new CharSequenceSubSequence(myChars, myOffset1, myOffset2);
}
@NotNull
public CharSequence getNotIgnoredContent(@NotNull BitSet ignored) {
StringBuilder sb = new StringBuilder();
for (int i = myOffset1; i < myOffset2; i++) {
if (ignored.get(i)) continue;
sb.append(myChars.charAt(i));
}
return sb.toString();
}
@NotNull
public CharSequence getOriginalText() {
return myChars;
}
}
}
@@ -22,6 +22,7 @@ import com.intellij.diff.util.IntPair
import com.intellij.diff.util.MergeRange
import com.intellij.diff.util.Range
import com.intellij.openapi.util.text.StringUtil.isWhiteSpace
import java.util.*
fun isPunctuation(c: Char): Boolean {
if (c == '_') return false
@@ -42,6 +43,11 @@ fun trim(text: CharSequence, start: Int, end: Int): IntPair {
{ index -> isWhiteSpace(text[index]) })
}
fun trim(start: Int, end: Int, ignored: BitSet): IntPair {
return trim(start, end,
{ index -> ignored[index] })
}
fun trimStart(text: CharSequence, start: Int, end: Int): Int {
return trimStart(start, end,
{ index -> isWhiteSpace(text[index]) })
@@ -156,12 +162,25 @@ fun expandWhitespacesBackward(text1: CharSequence, text2: CharSequence, text3: C
}
fun expandIW(text1: CharSequence, text2: CharSequence,
start1: Int, start2: Int, end1: Int, end2: Int): Range {
fun <T> trimExpandList(text1: List<T>, text2: List<T>,
start1: Int, start2: Int, end1: Int, end2: Int,
equals: (T, T) -> Boolean,
ignored1: (T) -> Boolean,
ignored2: (T) -> Boolean): Range {
return trimExpand(start1, start2, end1, end2,
{ index1, index2 -> equals(text1[index1], text2[index2]) },
{ index -> ignored1(text1[index]) },
{ index -> ignored2(text2[index]) })
}
fun trimExpandText(text1: CharSequence, text2: CharSequence,
start1: Int, start2: Int, end1: Int, end2: Int,
ignored1: BitSet,
ignored2: BitSet): Range {
return trimExpand(start1, start2, end1, end2,
{ index1, index2 -> text1[index1] == text2[index2] },
{ index -> isWhiteSpace(text1[index]) },
{ index -> isWhiteSpace(text2[index]) })
{ index -> ignored1[index] },
{ index -> ignored2[index] })
}
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.lang;
import com.intellij.diff.contents.DiffContent;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public interface DiffIgnoredRangeProvider {
ExtensionPointName<DiffIgnoredRangeProvider> EP_NAME =
ExtensionPointName.create("com.intellij.diff.lang.DiffIgnoredRangeProvider");
@NotNull
String getDescription();
boolean accepts(@Nullable Project project, @NotNull DiffContent content);
@NotNull
List<TextRange> getIgnoredRanges(@Nullable Project project, @NotNull CharSequence text, @NotNull DiffContent content);
}
@@ -0,0 +1,72 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.lang;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.lang.Language;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.LanguageSubstitutors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public abstract class LangDiffIgnoredRangeProvider implements DiffIgnoredRangeProvider {
protected abstract boolean accepts(@NotNull Project project, @NotNull Language language);
@NotNull
protected abstract List<TextRange> computeIgnoredRanges(@NotNull Project project, @NotNull CharSequence text, @NotNull Language language);
@Override
public final boolean accepts(@Nullable Project project, @NotNull DiffContent content) {
if (project == null) return false;
Language language = getLanguage(project, content);
if (language == null) return false;
return accepts(project, language);
}
@NotNull
@Override
public List<TextRange> getIgnoredRanges(@Nullable Project project, @NotNull CharSequence text, @NotNull DiffContent content) {
assert project != null;
Language language = getLanguage(project, content);
assert language != null;
return computeIgnoredRanges(project, text, language);
}
@Nullable
private static Language getLanguage(@NotNull Project project, @NotNull DiffContent content) {
Language language = content.getUserData(DiffUserDataKeys.LANGUAGE);
if (language != null) return language;
FileType type = content.getContentType();
if (type instanceof LanguageFileType) language = ((LanguageFileType)type).getLanguage();
if (language != null && content instanceof DocumentContent) {
VirtualFile highlightFile = ((DocumentContent)content).getHighlightFile();
if (highlightFile != null) language = LanguageSubstitutors.INSTANCE.substituteLanguage(language, highlightFile, project);
}
return language;
}
}
@@ -122,7 +122,7 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
myEditorSettingsAction = new SetEditorSettingsAction(getTextSettings(), getEditors());
myEditorSettingsAction.applyDefaults();
myTextDiffProvider = DiffUtil.createNoIgnoreTextDiffProvider(getRequest(), getTextSettings(), this::rediff);
myTextDiffProvider = DiffUtil.createNoIgnoreTextDiffProvider(getProject(), getRequest(), getTextSettings(), this::rediff);
new MyOpenInEditorWithMouseAction().install(getEditors());
@@ -80,7 +80,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
myModifierProvider = new ModifierProvider();
myTextDiffProvider = DiffUtil.createTextDiffProvider(getRequest(), getTextSettings(), this::rediff);
myTextDiffProvider = DiffUtil.createTextDiffProvider(getProject(), getRequest(), getTextSettings(), this::rediff);
DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
@@ -56,7 +56,7 @@ public class SimpleOnesideDiffViewer extends OnesideTextDiffViewer {
public SimpleOnesideDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, (ContentDiffRequest)request);
myTextDiffProvider = DiffUtil.createTextDiffProvider(getRequest(), getTextSettings(), this::rediff);
myTextDiffProvider = DiffUtil.createTextDiffProvider(getProject(), getRequest(), getTextSettings(), this::rediff);
}
@Override
@@ -22,7 +22,8 @@ public enum IgnorePolicy {
DEFAULT("Do not ignore"),
TRIM_WHITESPACES("Trim whitespaces"),
IGNORE_WHITESPACES("Ignore whitespaces"),
IGNORE_WHITESPACES_CHUNKS("Ignore whitespaces and empty lines");
IGNORE_WHITESPACES_CHUNKS("Ignore whitespaces and empty lines"),
FORMATTING("Ignore formatting");
@NotNull private final String myText;
@@ -46,6 +47,8 @@ public enum IgnorePolicy {
return ComparisonPolicy.IGNORE_WHITESPACES;
case IGNORE_WHITESPACES_CHUNKS:
return ComparisonPolicy.IGNORE_WHITESPACES;
case FORMATTING:
return ComparisonPolicy.DEFAULT;
default:
throw new IllegalArgumentException(this.name());
}
@@ -336,6 +336,9 @@ public class TextDiffViewerUtil {
if (value == IgnorePolicy.IGNORE_WHITESPACES_CHUNKS) {
return list(IgnorePolicy.IGNORE_WHITESPACES);
}
if (value == IgnorePolicy.FORMATTING) {
return list(IgnorePolicy.TRIM_WHITESPACES);
}
return list(IgnorePolicy.DEFAULT);
}
@@ -25,15 +25,14 @@ import com.intellij.diff.util.DiffUserDataKeysEx;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.intellij.diff.tools.util.base.HighlightPolicy.*;
import static com.intellij.diff.tools.util.base.IgnorePolicy.*;
public class SimpleTextDiffProvider extends TextDiffProviderBase implements TwosideTextDiffProvider {
private static final DiffUserDataKeysEx.DiffComputer DEFAULT_COMPUTER = (text1, text2, policy, innerChanges, indicator) -> {
public class SimpleTextDiffProvider extends TwosideTextDiffProviderBase implements TwosideTextDiffProvider {
static final DiffUserDataKeysEx.DiffComputer DEFAULT_COMPUTER = (text1, text2, policy, innerChanges, indicator) -> {
if (innerChanges) {
return ComparisonManager.getInstance().compareLinesInner(text1, text2, policy, indicator);
}
@@ -67,25 +66,15 @@ public class SimpleTextDiffProvider extends TextDiffProviderBase implements Twos
myDiffComputer = diffComputer;
}
@Nullable
@NotNull
@Override
public List<LineFragment> compare(@NotNull CharSequence text1, @NotNull CharSequence text2, @NotNull ProgressIndicator indicator) {
IgnorePolicy ignorePolicy = getIgnorePolicy();
HighlightPolicy highlightPolicy = getHighlightPolicy();
if (!highlightPolicy.isShouldCompare()) return null;
protected List<LineFragment> doCompare(@NotNull CharSequence text1,
@NotNull CharSequence text2,
@NotNull IgnorePolicy ignorePolicy,
boolean innerFragments,
@NotNull ProgressIndicator indicator) {
ComparisonPolicy policy = ignorePolicy.getComparisonPolicy();
boolean innerFragments = highlightPolicy.isFineFragments();
boolean squashFragments = highlightPolicy.isShouldSquash();
boolean trimFragments = ignorePolicy.isShouldTrimChunks();
indicator.checkCanceled();
List<LineFragment> fragments = myDiffComputer.compute(text1, text2, policy, innerFragments, indicator);
indicator.checkCanceled();
return ComparisonManager.getInstance().processBlocks(fragments, text1, text2,
policy, squashFragments, trimFragments);
return myDiffComputer.compute(text1, text2, policy, innerFragments, indicator);
}
@@ -0,0 +1,167 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.tools.util.text;
import com.intellij.diff.comparison.ComparisonManagerImpl;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.lang.DiffIgnoredRangeProvider;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.tools.util.base.HighlightPolicy;
import com.intellij.diff.tools.util.base.IgnorePolicy;
import com.intellij.diff.tools.util.base.TextDiffSettingsHolder.TextDiffSettings;
import com.intellij.diff.util.Side;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.intellij.diff.tools.util.base.HighlightPolicy.*;
import static com.intellij.diff.tools.util.base.IgnorePolicy.*;
public class SmartTextDiffProvider extends TwosideTextDiffProviderBase implements TwosideTextDiffProvider {
private static final IgnorePolicy[] IGNORE_POLICIES = {DEFAULT, TRIM_WHITESPACES, IGNORE_WHITESPACES, IGNORE_WHITESPACES_CHUNKS, FORMATTING};
private static final HighlightPolicy[] HIGHLIGHT_POLICIES = {BY_LINE, BY_WORD, BY_WORD_SPLIT, DO_NOT_HIGHLIGHT};
@Nullable private final Project myProject;
@NotNull private final DiffContent myContent1;
@NotNull private final DiffContent myContent2;
@NotNull private final DiffIgnoredRangeProvider myProvider;
@Nullable
public static TwosideTextDiffProvider create(@Nullable Project project,
@NotNull ContentDiffRequest request,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff) {
if (!Registry.is("diff.smart.ignore.enabled")) return null;
DiffContent content1 = Side.LEFT.select(request.getContents());
DiffContent content2 = Side.RIGHT.select(request.getContents());
DiffIgnoredRangeProvider ignoredRangeProvider = getIgnoredRangeProvider(project, content1, content2);
if (ignoredRangeProvider == null) return null;
return new SmartTextDiffProvider(project, content1, content2, settings, rediff, ignoredRangeProvider);
}
@Nullable
public static TwosideTextDiffProvider.NoIgnore createNoIgnore(@Nullable Project project,
@NotNull ContentDiffRequest request,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff) {
if (!Registry.is("diff.smart.ignore.enabled")) return null;
DiffContent content1 = Side.LEFT.select(request.getContents());
DiffContent content2 = Side.RIGHT.select(request.getContents());
DiffIgnoredRangeProvider ignoredRangeProvider = getIgnoredRangeProvider(project, content1, content2);
if (ignoredRangeProvider == null) return null;
return new SmartTextDiffProvider.NoIgnore(project, content1, content2, settings, rediff, ignoredRangeProvider);
}
private SmartTextDiffProvider(@Nullable Project project,
@NotNull DiffContent content1,
@NotNull DiffContent content2,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff,
@NotNull DiffIgnoredRangeProvider ignoredRangeProvider) {
this(project, content1, content2, settings, rediff, ignoredRangeProvider, IGNORE_POLICIES, HIGHLIGHT_POLICIES);
}
private SmartTextDiffProvider(@Nullable Project project,
@NotNull DiffContent content1,
@NotNull DiffContent content2,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff,
@NotNull DiffIgnoredRangeProvider ignoredRangeProvider,
@NotNull IgnorePolicy[] ignorePolicies,
@NotNull HighlightPolicy[] highlightPolicies) {
super(settings, rediff, ignorePolicies, highlightPolicies);
myProject = project;
myContent1 = content1;
myContent2 = content2;
myProvider = ignoredRangeProvider;
}
@Nullable
@Override
protected String getText(@NotNull IgnorePolicy option) {
if (option == FORMATTING) return myProvider.getDescription();
return null;
}
@NotNull
@Override
protected List<LineFragment> doCompare(@NotNull CharSequence text1,
@NotNull CharSequence text2,
@NotNull IgnorePolicy ignorePolicy,
boolean innerFragments,
@NotNull ProgressIndicator indicator) {
if (ignorePolicy == FORMATTING) {
return compareIgnoreFormatting(text1, text2, innerFragments, indicator);
}
else {
ComparisonPolicy policy = ignorePolicy.getComparisonPolicy();
return SimpleTextDiffProvider.DEFAULT_COMPUTER.compute(text1, text2, policy, innerFragments, indicator);
}
}
@NotNull
private List<LineFragment> compareIgnoreFormatting(@NotNull CharSequence text1,
@NotNull CharSequence text2,
boolean innerFragments,
@NotNull ProgressIndicator indicator) {
List<TextRange> ranges1 = myProvider.getIgnoredRanges(myProject, text1, myContent1);
List<TextRange> ranges2 = myProvider.getIgnoredRanges(myProject, text2, myContent2);
ComparisonManagerImpl comparisonManager = ComparisonManagerImpl.getInstanceImpl();
return comparisonManager.compareLinesWithIgnoredRanges(text1, text2, ranges1, ranges2, innerFragments, indicator);
}
@Nullable
private static DiffIgnoredRangeProvider getIgnoredRangeProvider(@Nullable Project project,
@NotNull DiffContent content1,
@NotNull DiffContent content2) {
for (DiffIgnoredRangeProvider provider : DiffIgnoredRangeProvider.EP_NAME.getExtensions()) {
if (provider.accepts(project, content1) &&
provider.accepts(project, content2)) {
return provider;
}
}
return null;
}
public static class NoIgnore extends SmartTextDiffProvider implements TwosideTextDiffProvider.NoIgnore {
private NoIgnore(@Nullable Project project,
@NotNull DiffContent content1,
@NotNull DiffContent content2,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff,
@NotNull DiffIgnoredRangeProvider ignoredRangeProvider) {
super(project, content1, content2, settings, rediff, ignoredRangeProvider,
IGNORE_POLICIES, ArrayUtil.remove(HIGHLIGHT_POLICIES, DO_NOT_HIGHLIGHT));
}
@NotNull
@Override
public List<LineFragment> compare(@NotNull CharSequence text1, @NotNull CharSequence text2, @NotNull ProgressIndicator indicator) {
//noinspection ConstantConditions
return super.compare(text1, text2, indicator);
}
}
}
@@ -23,9 +23,11 @@ import com.intellij.diff.tools.util.base.TextDiffViewerUtil.IgnorePolicySettingA
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.Separator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.intellij.openapi.util.text.StringUtil.notNullize;
import static com.intellij.util.containers.ContainerUtil.list;
public class TextDiffProviderBase implements TextDiffProvider {
@@ -36,8 +38,8 @@ public class TextDiffProviderBase implements TextDiffProvider {
@NotNull Runnable rediff,
@NotNull IgnorePolicy[] ignorePolicies,
@NotNull HighlightPolicy[] highlightPolicies) {
myIgnorePolicySettingAction = new IgnorePolicySettingAction(settings, rediff, ignorePolicies);
myHighlightPolicySettingAction = new HighlightPolicySettingAction(settings, rediff, highlightPolicies);
myIgnorePolicySettingAction = new MyIgnorePolicySettingAction(settings, rediff, ignorePolicies);
myHighlightPolicySettingAction = new MyHighlightPolicySettingAction(settings, rediff, highlightPolicies);
}
@NotNull
@@ -69,4 +71,44 @@ public class TextDiffProviderBase implements TextDiffProvider {
public boolean isHighlightingDisabled() {
return myHighlightPolicySettingAction.getValue() == HighlightPolicy.DO_NOT_HIGHLIGHT;
}
@Nullable
protected String getText(@NotNull IgnorePolicy option) {
return null;
}
@Nullable
protected String getText(@NotNull HighlightPolicy option) {
return null;
}
private class MyIgnorePolicySettingAction extends IgnorePolicySettingAction {
public MyIgnorePolicySettingAction(@NotNull TextDiffSettings settings,
@NotNull Runnable rediff,
@NotNull IgnorePolicy[] ignorePolicies) {
super(settings, rediff, ignorePolicies);
}
@NotNull
@Override
protected String getText(@NotNull IgnorePolicy option) {
return notNullize(TextDiffProviderBase.this.getText(option), super.getText(option));
}
}
private class MyHighlightPolicySettingAction extends HighlightPolicySettingAction {
public MyHighlightPolicySettingAction(@NotNull TextDiffSettings settings,
@NotNull Runnable rediff,
@NotNull HighlightPolicy[] highlightPolicies) {
super(settings, rediff, highlightPolicies);
}
@NotNull
@Override
protected String getText(@NotNull HighlightPolicy option) {
return notNullize(TextDiffProviderBase.this.getText(option), super.getText(option));
}
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.tools.util.text;
import com.intellij.diff.comparison.ComparisonManager;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.tools.util.base.HighlightPolicy;
import com.intellij.diff.tools.util.base.IgnorePolicy;
import com.intellij.diff.tools.util.base.TextDiffSettingsHolder.TextDiffSettings;
import com.intellij.openapi.progress.ProgressIndicator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
abstract class TwosideTextDiffProviderBase extends TextDiffProviderBase implements TwosideTextDiffProvider {
protected TwosideTextDiffProviderBase(@NotNull TextDiffSettings settings,
@NotNull Runnable rediff,
@NotNull IgnorePolicy[] ignorePolicies,
@NotNull HighlightPolicy[] highlightPolicies) {
super(settings, rediff, ignorePolicies, highlightPolicies);
}
@Nullable
@Override
public List<LineFragment> compare(@NotNull CharSequence text1, @NotNull CharSequence text2, @NotNull ProgressIndicator indicator) {
IgnorePolicy ignorePolicy = getIgnorePolicy();
HighlightPolicy highlightPolicy = getHighlightPolicy();
if (!highlightPolicy.isShouldCompare()) return null;
ComparisonPolicy policy = ignorePolicy.getComparisonPolicy();
boolean innerFragments = highlightPolicy.isFineFragments();
boolean squashFragments = highlightPolicy.isShouldSquash();
boolean trimFragments = ignorePolicy.isShouldTrimChunks();
indicator.checkCanceled();
List<LineFragment> fragments = doCompare(text1, text2, ignorePolicy, innerFragments, indicator);
indicator.checkCanceled();
return ComparisonManager.getInstance().processBlocks(fragments, text1, text2,
policy, squashFragments, trimFragments);
}
@NotNull
protected abstract List<LineFragment> doCompare(@NotNull CharSequence text1,
@NotNull CharSequence text2,
@NotNull IgnorePolicy ignorePolicy,
boolean innerFragments,
@NotNull ProgressIndicator indicator);
}
@@ -36,10 +36,7 @@ import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.tools.util.base.TextDiffSettingsHolder.TextDiffSettings;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.text.LineOffsets;
import com.intellij.diff.tools.util.text.MergeInnerDifferences;
import com.intellij.diff.tools.util.text.SimpleTextDiffProvider;
import com.intellij.diff.tools.util.text.TwosideTextDiffProvider;
import com.intellij.diff.tools.util.text.*;
import com.intellij.icons.AllIcons;
import com.intellij.lang.Language;
import com.intellij.openapi.Disposable;
@@ -600,24 +597,30 @@ public class DiffUtil {
//
@NotNull
public static TwosideTextDiffProvider createTextDiffProvider(@NotNull ContentDiffRequest request,
public static TwosideTextDiffProvider createTextDiffProvider(@Nullable Project project,
@NotNull ContentDiffRequest request,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff) {
DiffUserDataKeysEx.DiffComputer diffComputer = request.getUserData(DiffUserDataKeysEx.CUSTOM_DIFF_COMPUTER);
if (diffComputer != null) {
return new SimpleTextDiffProvider(settings, rediff, diffComputer);
}
if (diffComputer != null) return new SimpleTextDiffProvider(settings, rediff, diffComputer);
TwosideTextDiffProvider smartProvider = SmartTextDiffProvider.create(project, request, settings, rediff);
if (smartProvider != null) return smartProvider;
return new SimpleTextDiffProvider(settings, rediff);
}
@NotNull
public static TwosideTextDiffProvider.NoIgnore createNoIgnoreTextDiffProvider(@NotNull ContentDiffRequest request,
public static TwosideTextDiffProvider.NoIgnore createNoIgnoreTextDiffProvider(@Nullable Project project,
@NotNull ContentDiffRequest request,
@NotNull TextDiffSettings settings,
@NotNull Runnable rediff) {
DiffUserDataKeysEx.DiffComputer diffComputer = request.getUserData(DiffUserDataKeysEx.CUSTOM_DIFF_COMPUTER);
if (diffComputer != null) {
return new SimpleTextDiffProvider.NoIgnore(settings, rediff, diffComputer);
}
if (diffComputer != null) return new SimpleTextDiffProvider.NoIgnore(settings, rediff, diffComputer);
TwosideTextDiffProvider.NoIgnore smartProvider = SmartTextDiffProvider.createNoIgnore(project, request, settings, rediff);
if (smartProvider != null) return smartProvider;
return new SimpleTextDiffProvider.NoIgnore(settings, rediff);
}
@@ -993,6 +993,8 @@
<automaticRenamerFactory implementation="com.intellij.refactoring.rename.RelatedFilesRenamerFactory"/>
<refactoring.moveHandler implementation="com.intellij.refactoring.move.moveFilesOrDirectories.MoveRelatedFilesHandler"
order="before moveFileOrDir"/>
<diff.lang.DiffIgnoredRangeProvider implementation="com.intellij.diff.lang.JavaDiffIgnoredRangeProvider"/>
</extensions>
</idea-plugin>
@@ -254,6 +254,7 @@
<extensionPoint qualifiedName="com.intellij.diff.DiffTool" interface="com.intellij.diff.DiffTool"/>
<extensionPoint qualifiedName="com.intellij.diff.merge.MergeTool" interface="com.intellij.diff.merge.MergeTool"/>
<extensionPoint qualifiedName="com.intellij.diff.DiffExtension" interface="com.intellij.diff.DiffExtension"/>
<extensionPoint qualifiedName="com.intellij.diff.lang.DiffIgnoredRangeProvider" interface="com.intellij.diff.lang.DiffIgnoredRangeProvider"/>
<extensionPoint name="testStatusListener" interface="com.intellij.execution.testframework.TestStatusListener"/>
<extensionPoint name="runConfigurationBeforeRunProviderDelegate" interface="com.intellij.execution.impl.RunConfigurationBeforeRunProviderDelegate"/>
@@ -531,6 +531,8 @@ diff.merge.resolve.conflict.action.use.greedy.approach.description=Use greedy he
diff.enable.psi.highlighting=true
diff.enable.psi.highlighting.description=Enable advanced highlighting and code navigation in VCS content in diff viewers.
diff.pass.rich.editor.context=false
diff.smart.ignore.enabled=true
diff.smart.ignore.enabled.description=Enable syntax-aware ignore option in text diff viewer
search.everywhere.toolwindows=true
search.everywhere.files=true