| /* |
| * Copyright 2000-2013 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.openapi.diff.impl.fragments; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.diff.impl.string.DiffString; |
| import com.intellij.openapi.diff.impl.highlighting.FragmentSide; |
| import com.intellij.openapi.diff.impl.util.TextDiffTypeEnum; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.TextRange; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| |
| public class LineFragment extends LineBlock implements Fragment { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.fragments.LineFragment"); |
| private final TextRange myRange1; |
| private final TextRange myRange2; |
| private FragmentList myChildren; |
| private boolean myHasLineChildren; |
| |
| public LineFragment(int startingLine1, int modifiedLines1, |
| int startingLine2, int modifiedLines2, |
| TextDiffTypeEnum blockType, TextRange range1, TextRange range2) { |
| this(startingLine1, modifiedLines1, |
| startingLine2, modifiedLines2, |
| blockType, range1, range2, FragmentList.EMPTY); |
| } |
| |
| private LineFragment(int startingLine1, int modifiedLines1, |
| int startingLine2, int modifiedLines2, |
| TextDiffTypeEnum blockType, TextRange range1, TextRange range2, FragmentList children) { |
| super(startingLine1, modifiedLines1, startingLine2, modifiedLines2, blockType); |
| LOG.assertTrue(modifiedLines1 > 0 || modifiedLines2 > 0); |
| myRange1 = range1; |
| myRange2 = range2; |
| myChildren = children; |
| checkChildren(myChildren.iterator()); |
| } |
| |
| |
| @Override |
| public TextRange getRange(FragmentSide side) { |
| if (side == FragmentSide.SIDE1) return myRange1; |
| if (side == FragmentSide.SIDE2) return myRange2; |
| throw new IllegalArgumentException(String.valueOf(side)); |
| } |
| |
| @Override |
| public Fragment shift(TextRange range1, TextRange range2, int startingLine1, int startingLine2) { |
| return new LineFragment(startingLine1 + getStartingLine1(), getModifiedLines1(), |
| startingLine2 + getStartingLine2(), getModifiedLines2(), |
| getType(), shiftRange(range1, myRange1), shiftRange(range2, myRange2), |
| myChildren.shift(range1, range2, startingLine1, startingLine2)); |
| } |
| |
| /** |
| * <p>Adjusts the diff type of this line fragment based on the types of the inline child fragments.</p> |
| * <p>For example, a modification in terms of line fragment may be just one inline insertion. |
| * In this case it is better to think about the whole change as of insertion.</p> |
| */ |
| public void adjustTypeFromChildrenTypes() { |
| if (getType() != TextDiffTypeEnum.CHANGED) { // if the change is already insertion or deletion, no need to adjust |
| return; |
| } |
| |
| TextDiffTypeEnum candidateType = null; |
| for (Iterator<Fragment> children = getChildrenIterator(); children != null && children.hasNext(); ) { |
| TextDiffTypeEnum fragmentType = children.next().getType(); |
| if (fragmentType == null) { |
| continue; |
| } |
| switch (fragmentType) { |
| case CHANGED: // inline change => everything is a change |
| return; |
| case INSERT: |
| if (candidateType == null) { |
| candidateType = TextDiffTypeEnum.INSERT; |
| } |
| else if (candidateType != TextDiffTypeEnum.INSERT) { |
| return; // different changes (insertion and deletion) inside a single line => everything is a change |
| } |
| break; |
| case DELETED: |
| if (candidateType == null) { |
| candidateType = TextDiffTypeEnum.DELETED; |
| } |
| else if (candidateType != TextDiffTypeEnum.DELETED) { |
| return; |
| } |
| break; |
| default: |
| // should not happen, because conflicts can happen only in merge tools, where there are no inline changes for now, |
| // but we don't want to modify the fragment in this doubtful case anyway. |
| return; |
| } |
| } |
| |
| if (candidateType != null) { |
| setType(candidateType); |
| } |
| } |
| |
| static TextRange shiftRange(TextRange shift, TextRange range) { |
| int start = shift.getStartOffset(); |
| int newEnd = start + range.getEndOffset(); |
| int newStart = start + range.getStartOffset(); |
| LOG.assertTrue(newStart <= shift.getEndOffset()); |
| LOG.assertTrue(newEnd <= shift.getEndOffset()); |
| return new TextRange(newStart, newEnd); |
| } |
| |
| @Override |
| public void highlight(FragmentHighlighter fragmentHighlighter) { |
| fragmentHighlighter.highlightLine(this); |
| } |
| |
| public boolean isOneSide() { |
| return myRange1.getLength() == 0 || myRange2.getLength() == 0; |
| } |
| |
| public boolean isEqual() { |
| return getType() == null; |
| } |
| |
| @Override |
| public Fragment getSubfragmentAt(int offset, FragmentSide side, Condition<Fragment> condition) { |
| Fragment childFragment = myChildren.getFragmentAt(offset, side, condition); |
| return childFragment != null ? childFragment : this; |
| } |
| |
| @Nullable |
| public Iterator<Fragment> getChildrenIterator() { |
| return myChildren == null || myChildren.isEmpty() ? null : myChildren.iterator(); |
| } |
| |
| @NotNull |
| public DiffString getText(@NotNull DiffString text, @NotNull FragmentSide side) { |
| TextRange range = getRange(side); |
| return text.substring(range.getStartOffset(), range.getEndOffset()); |
| } |
| |
| |
| public void addAllDescendantsTo(ArrayList<LineFragment> descendants) { |
| if (myChildren == null) return; |
| for (Iterator<Fragment> iterator = myChildren.iterator(); iterator.hasNext();) { |
| Fragment fragment = iterator.next(); |
| if (fragment instanceof LineFragment) { |
| LineFragment lineFragment = (LineFragment)fragment; |
| descendants.add(lineFragment); |
| lineFragment.addAllDescendantsTo(descendants); |
| } |
| } |
| } |
| |
| public void setChildren(ArrayList<Fragment> fragments) { |
| LOG.assertTrue(myChildren == FragmentList.EMPTY); |
| ArrayList<Fragment> shifted = |
| FragmentListImpl.shift(fragments, myRange1, myRange2, getStartingLine1(), getStartingLine2()); |
| if (shifted.isEmpty()) return; |
| Fragment firstChild = shifted.get(0); |
| if (shifted.size() == 1 && isSameRanges(firstChild)) { |
| if (!(firstChild instanceof LineFragment)) return; |
| LineFragment lineFragment = (LineFragment)firstChild; |
| myChildren = lineFragment.myChildren; |
| } else myChildren = FragmentListImpl.fromList(shifted); |
| checkChildren(myChildren.iterator()); |
| } |
| |
| private void checkChildren(Iterator<Fragment> iterator) { |
| if (myChildren.isEmpty()) { |
| myHasLineChildren = false; |
| return; |
| } |
| boolean hasLineChildren = false; |
| boolean hasInlineChildren = false; |
| while(iterator.hasNext()) { |
| Fragment fragment = iterator.next(); |
| boolean lineChild = fragment instanceof LineFragment; |
| hasLineChildren |= lineChild; |
| hasInlineChildren |= !lineChild; |
| if (lineChild) { |
| LineFragment lineFragment = (LineFragment)fragment; |
| LOG.assertTrue(getStartingLine1() != lineFragment.getStartingLine1() || |
| getModifiedLines1() != lineFragment.getModifiedLines1() || |
| getStartingLine2() != lineFragment.getStartingLine2() || |
| getModifiedLines2() != lineFragment.getModifiedLines2()); |
| } |
| } |
| LOG.assertTrue(hasLineChildren ^ hasInlineChildren); |
| myHasLineChildren = hasLineChildren; |
| } |
| |
| private boolean isSameRanges(Fragment fragment) { |
| return getRange(FragmentSide.SIDE1).equals(fragment.getRange(FragmentSide.SIDE1)) && |
| getRange(FragmentSide.SIDE2).equals(fragment.getRange(FragmentSide.SIDE2)); |
| } |
| |
| public boolean isHasLineChildren() { |
| return myHasLineChildren; |
| } |
| |
| @Override |
| public int getEndLine1() { |
| return super.getEndLine1(); |
| } |
| |
| @Override |
| public int getEndLine2() { |
| return super.getEndLine2(); |
| } |
| } |