blob: 57399b5b4220599a56dbd04cf1b1a4eb0d24ed65 [file] [log] [blame]
/*
* 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();
}
}