From 74921ef65bc34ad17f13f654148878c0b4746644 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Thu, 12 Jan 2017 14:51:04 +0300 Subject: [PATCH] IDEA-144761 diff: do not ignore whitespaces in string literals --- .../lang/JavaDiffIgnoredRangeProvider.java | 70 ++++++++ .../comparison/ComparisonManagerImpl.java | 147 +++++++++++++++ .../com/intellij/diff/comparison/TrimUtil.kt | 27 ++- .../diff/lang/DiffIgnoredRangeProvider.java | 38 ++++ .../lang/LangDiffIgnoredRangeProvider.java | 72 ++++++++ .../tools/fragmented/UnifiedDiffViewer.java | 2 +- .../diff/tools/simple/SimpleDiffViewer.java | 2 +- .../tools/simple/SimpleOnesideDiffViewer.java | 2 +- .../diff/tools/util/base/IgnorePolicy.java | 5 +- .../tools/util/base/TextDiffViewerUtil.java | 3 + .../util/text/SimpleTextDiffProvider.java | 29 +-- .../util/text/SmartTextDiffProvider.java | 167 ++++++++++++++++++ .../tools/util/text/TextDiffProviderBase.java | 46 ++++- .../text/TwosideTextDiffProviderBase.java | 65 +++++++ .../src/com/intellij/diff/util/DiffUtil.java | 27 +-- .../src/META-INF/LangExtensions.xml | 2 + .../src/META-INF/PlatformExtensionPoints.xml | 1 + .../util/resources/misc/registry.properties | 2 + 18 files changed, 665 insertions(+), 42 deletions(-) create mode 100644 java/java-impl/src/com/intellij/diff/lang/JavaDiffIgnoredRangeProvider.java create mode 100644 platform/diff-impl/src/com/intellij/diff/lang/DiffIgnoredRangeProvider.java create mode 100644 platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java create mode 100644 platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java create mode 100644 platform/diff-impl/src/com/intellij/diff/tools/util/text/TwosideTextDiffProviderBase.java diff --git a/java/java-impl/src/com/intellij/diff/lang/JavaDiffIgnoredRangeProvider.java b/java/java-impl/src/com/intellij/diff/lang/JavaDiffIgnoredRangeProvider.java new file mode 100644 index 000000000000..395c94b89fa9 --- /dev/null +++ b/java/java-impl/src/com/intellij/diff/lang/JavaDiffIgnoredRangeProvider.java @@ -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 computeIgnoredRanges(@NotNull Project project, @NotNull CharSequence text, @NotNull Language language) { + return ReadAction.compute(() -> { + List 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; + } +} diff --git a/platform/diff-impl/src/com/intellij/diff/comparison/ComparisonManagerImpl.java b/platform/diff-impl/src/com/intellij/diff/comparison/ComparisonManagerImpl.java index 2d2259ceaab3..516c316682b5 100644 --- a/platform/diff-impl/src/com/intellij/diff/comparison/ComparisonManagerImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/comparison/ComparisonManagerImpl.java @@ -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 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 compareLinesWithIgnoredRanges(@NotNull CharSequence text1, + @NotNull CharSequence text2, + @NotNull List ignoredRanges1, + @NotNull List ignoredRanges2, + boolean innerFragments, + @NotNull ProgressIndicator indicator) throws DiffTooBigException { + BitSet ignored1 = collectIgnoredRanges(ignoredRanges1); + BitSet ignored2 = collectIgnoredRanges(ignoredRanges2); + + List lines1 = getLines(text1); + List lines2 = getLines(text2); + + List lineTexts1 = ContainerUtil.map(lines1, line -> line.getNotIgnoredContent(ignored1)); + List lineTexts2 = ContainerUtil.map(lines2, line -> line.getNotIgnoredContent(ignored2)); + + FairDiffIterable iterable = ByLine.compare(lineTexts1, lineTexts2, ComparisonPolicy.DEFAULT, indicator); + List 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 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 lines1, + @NotNull List 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 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; + } } } diff --git a/platform/diff-impl/src/com/intellij/diff/comparison/TrimUtil.kt b/platform/diff-impl/src/com/intellij/diff/comparison/TrimUtil.kt index 718c64c8d188..e7d90aa5727f 100644 --- a/platform/diff-impl/src/com/intellij/diff/comparison/TrimUtil.kt +++ b/platform/diff-impl/src/com/intellij/diff/comparison/TrimUtil.kt @@ -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 trimExpandList(text1: List, text2: List, + 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] }) } diff --git a/platform/diff-impl/src/com/intellij/diff/lang/DiffIgnoredRangeProvider.java b/platform/diff-impl/src/com/intellij/diff/lang/DiffIgnoredRangeProvider.java new file mode 100644 index 000000000000..b9d26a2c5b58 --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/lang/DiffIgnoredRangeProvider.java @@ -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 EP_NAME = + ExtensionPointName.create("com.intellij.diff.lang.DiffIgnoredRangeProvider"); + + @NotNull + String getDescription(); + + boolean accepts(@Nullable Project project, @NotNull DiffContent content); + + @NotNull + List getIgnoredRanges(@Nullable Project project, @NotNull CharSequence text, @NotNull DiffContent content); +} diff --git a/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java b/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java new file mode 100644 index 000000000000..02aa1342e075 --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/lang/LangDiffIgnoredRangeProvider.java @@ -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 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 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; + } +} diff --git a/platform/diff-impl/src/com/intellij/diff/tools/fragmented/UnifiedDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/fragmented/UnifiedDiffViewer.java index 9a727b4bc03e..e349a8d24e94 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/fragmented/UnifiedDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/fragmented/UnifiedDiffViewer.java @@ -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()); diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java index a4f3e08144e8..957d5645f9cc 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java @@ -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); diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleOnesideDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleOnesideDiffViewer.java index dbf05e00e8cc..c71919ade10d 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleOnesideDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleOnesideDiffViewer.java @@ -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 diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/base/IgnorePolicy.java b/platform/diff-impl/src/com/intellij/diff/tools/util/base/IgnorePolicy.java index 8c38a2af350c..80642ecf391b 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/base/IgnorePolicy.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/base/IgnorePolicy.java @@ -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()); } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerUtil.java b/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerUtil.java index 5e04606409ad..75a29ea24570 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerUtil.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerUtil.java @@ -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); } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleTextDiffProvider.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleTextDiffProvider.java index a28d4bf95e57..0935043c9372 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleTextDiffProvider.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleTextDiffProvider.java @@ -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 compare(@NotNull CharSequence text1, @NotNull CharSequence text2, @NotNull ProgressIndicator indicator) { - IgnorePolicy ignorePolicy = getIgnorePolicy(); - HighlightPolicy highlightPolicy = getHighlightPolicy(); - - if (!highlightPolicy.isShouldCompare()) return null; - + protected List 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 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); } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java new file mode 100644 index 000000000000..db8ee6ccce7a --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SmartTextDiffProvider.java @@ -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 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 compareIgnoreFormatting(@NotNull CharSequence text1, + @NotNull CharSequence text2, + boolean innerFragments, + @NotNull ProgressIndicator indicator) { + List ranges1 = myProvider.getIgnoredRanges(myProject, text1, myContent1); + List 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 compare(@NotNull CharSequence text1, @NotNull CharSequence text2, @NotNull ProgressIndicator indicator) { + //noinspection ConstantConditions + return super.compare(text1, text2, indicator); + } + } +} diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/TextDiffProviderBase.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/TextDiffProviderBase.java index 62134cf7b089..b694652d147b 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/text/TextDiffProviderBase.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/TextDiffProviderBase.java @@ -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)); + } + } } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/TwosideTextDiffProviderBase.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/TwosideTextDiffProviderBase.java new file mode 100644 index 000000000000..c774f0879845 --- /dev/null +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/TwosideTextDiffProviderBase.java @@ -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 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 fragments = doCompare(text1, text2, ignorePolicy, innerFragments, indicator); + + indicator.checkCanceled(); + return ComparisonManager.getInstance().processBlocks(fragments, text1, text2, + policy, squashFragments, trimFragments); + } + + @NotNull + protected abstract List doCompare(@NotNull CharSequence text1, + @NotNull CharSequence text2, + @NotNull IgnorePolicy ignorePolicy, + boolean innerFragments, + @NotNull ProgressIndicator indicator); +} diff --git a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java index 0904028a4f5c..e9dacd3238b7 100644 --- a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java +++ b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java @@ -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); } diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index ebaceecb4428..5f458bcf1dbe 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -993,6 +993,8 @@ + + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index 83a73f0b1134..b2d740f89352 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -254,6 +254,7 @@ + diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index bc703565a9a1..74213e628e68 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -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