blob: 5f7141d9b4d0a0b40d110dd21edf9f582159e174 [file] [log] [blame]
/*
* Copyright 2000-2009 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.incrementalMerge;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.diff.DiffContent;
import com.intellij.openapi.diff.DiffRequest;
import com.intellij.openapi.diff.ex.DiffFragment;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.diff.impl.incrementalMerge.ui.MergePanel2;
import com.intellij.openapi.diff.impl.processing.DiffPolicy;
import com.intellij.openapi.diff.impl.string.DiffString;
import com.intellij.openapi.diff.impl.util.ContextLogger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.diff.FilesTooBigForDiffException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class MergeList implements UserDataHolder {
public static final FragmentSide BRANCH_SIDE = FragmentSide.SIDE2;
public static final FragmentSide BASE_SIDE = FragmentSide.SIDE1;
public static final DataKey<MergeList> DATA_KEY = DataKey.create("mergeList");
public static final Condition<Change> NOT_CONFLICTS = new Condition<Change>() {
public boolean value(Change change) {
return !(change instanceof ConflictChange);
}
};
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.incrementalMerge.MergeList");
@NotNull private final UserDataHolderBase myDataHolder = new UserDataHolderBase();
@NotNull private final ChangeList myBaseToLeftChangeList;
@NotNull private final ChangeList myBaseToRightChangeList;
@Nullable private final String myErrorMessage;
private MergeList(@Nullable Project project,
@NotNull Document left,
@NotNull Document base,
@NotNull Document right,
@Nullable String errorMessage) {
myBaseToLeftChangeList = new ChangeList(base, left, project);
myBaseToRightChangeList = new ChangeList(base, right, project);
myErrorMessage = errorMessage;
}
@NotNull
public ChangeList getLeftChangeList() {
return myBaseToLeftChangeList;
}
@NotNull
public ChangeList getRightChangeList() {
return myBaseToRightChangeList;
}
@NotNull
public static MergeList create(@Nullable Project project, @NotNull Document left, @NotNull Document base, @NotNull Document right) {
MergeList mergeList;
String leftText = left.getText();
String baseText = base.getText();
String rightText = right.getText();
@NonNls final Object[] data = {
"Left\n", leftText,
"\nBase\n", baseText,
"\nRight\n", rightText
};
ContextLogger logger = new ContextLogger(LOG, new ContextLogger.SimpleContext(data));
List<MergeFragment> fragmentList;
try {
fragmentList = processText(leftText, baseText, rightText, logger);
mergeList = new MergeList(project, left, base, right, null);
}
catch (FilesTooBigForDiffException e) {
fragmentList = Collections.emptyList();
mergeList = new MergeList(project, left, base, right, e.getMessage());
}
ArrayList<Change> leftChanges = new ArrayList<Change>();
ArrayList<Change> rightChanges = new ArrayList<Change>();
for (MergeFragment mergeFragment : fragmentList) {
TextRange baseRange = mergeFragment.getBase();
TextRange leftRange = mergeFragment.getLeft();
TextRange rightRange = mergeFragment.getRight();
if (compareSubstring(leftText, leftRange, rightText, rightRange)) {
MergeNoConflict conflict = new MergeNoConflict(baseRange, leftRange, rightRange, mergeList);
assert conflict.getLeftChange() != null;
assert conflict.getRightChange() != null;
leftChanges.add(conflict.getLeftChange());
rightChanges.add(conflict.getRightChange());
}
else if (compareSubstring(baseText, baseRange, leftText, leftRange)) {
rightChanges.add(SimpleChange.fromRanges(baseRange, rightRange, mergeList.myBaseToRightChangeList));
}
else if (compareSubstring(baseText, baseRange, rightText, rightRange)) {
leftChanges.add(SimpleChange.fromRanges(baseRange, leftRange, mergeList.myBaseToLeftChangeList));
}
else {
MergeConflict conflict = new MergeConflict(baseRange, leftRange, rightRange, mergeList);
assert conflict.getLeftChange() != null;
assert conflict.getRightChange() != null;
leftChanges.add(conflict.getLeftChange());
rightChanges.add(conflict.getRightChange());
}
}
mergeList.myBaseToLeftChangeList.setChanges(leftChanges);
mergeList.myBaseToRightChangeList.setChanges(rightChanges);
return mergeList;
}
private static boolean compareSubstring(@NotNull String text1,
@NotNull TextRange range1,
@NotNull String text2,
@NotNull TextRange range2) {
if (range1.getLength() != range2.getLength()) return false;
int index1 = range1.getStartOffset();
int index2 = range2.getStartOffset();
while (index1 < range1.getEndOffset()) {
if (text1.charAt(index1) != text2.charAt(index2)) return false;
index1++;
index2++;
}
return true;
}
@NotNull
private static List<MergeFragment> processText(@NotNull String leftText,
@NotNull String baseText,
@NotNull String rightText,
@NotNull ContextLogger logger) throws FilesTooBigForDiffException {
DiffFragment[] leftFragments = DiffPolicy.DEFAULT_LINES.buildFragments(DiffString.create(baseText), DiffString.create(leftText));
DiffFragment[] rightFragments = DiffPolicy.DEFAULT_LINES.buildFragments(DiffString.create(baseText), DiffString.create(rightText));
int[] leftOffsets = {0, 0};
int[] rightOffsets = {0, 0};
int leftIndex = 0;
int rightIndex = 0;
MergeBuilder builder = new MergeBuilder(logger);
while (leftIndex < leftFragments.length || rightIndex < rightFragments.length) {
FragmentSide side;
TextRange[] equalRanges = new TextRange[2];
if (leftOffsets[0] < rightOffsets[0] && leftIndex < leftFragments.length) {
side = FragmentSide.SIDE1;
getEqualRanges(leftFragments[leftIndex], leftOffsets, equalRanges);
leftIndex++;
} else if (rightIndex < rightFragments.length) {
side = FragmentSide.SIDE2;
getEqualRanges(rightFragments[rightIndex], rightOffsets, equalRanges);
rightIndex++;
} else break;
if (equalRanges[0] != null && equalRanges[1] != null) builder.add(equalRanges[0], equalRanges[1], side);
else logger.assertTrue(equalRanges[0] == null && equalRanges[1] == null);
}
return builder.finish(leftText.length(), baseText.length(), rightText.length());
}
private static void getEqualRanges(@NotNull DiffFragment fragment, @NotNull int[] leftOffsets, @NotNull TextRange[] equalRanges) {
int baseLength = getTextLength(fragment.getText1());
int versionLength = getTextLength(fragment.getText2());
if (fragment.isEqual()) {
equalRanges[0] = new TextRange(leftOffsets[0], leftOffsets[0] + baseLength);
equalRanges[1] = new TextRange(leftOffsets[1], leftOffsets[1] + versionLength);
} else {
equalRanges[0] = null;
equalRanges[1] = null;
}
leftOffsets[0] += baseLength;
leftOffsets[1] += versionLength;
}
private static int getTextLength(@Nullable DiffString text1) {
return text1 != null ? text1.length() : 0;
}
public static MergeList create(@NotNull DiffRequest data) {
DiffContent[] contents = data.getContents();
return create(data.getProject(), contents[0].getDocument(), contents[1].getDocument(), contents[2].getDocument());
}
public void setMarkups(Editor left, Editor base, Editor right) {
myBaseToLeftChangeList.setMarkup(base, left);
myBaseToRightChangeList.setMarkup(base, right);
addActions(FragmentSide.SIDE1);
addActions(FragmentSide.SIDE2);
}
public Iterator<Change> getAllChanges() {
return ContainerUtil.concatIterators(myBaseToLeftChangeList.getChanges().iterator(), myBaseToRightChangeList.getChanges().iterator());
}
public void addListener(ChangeList.Listener listener) {
myBaseToLeftChangeList.addListener(listener);
myBaseToRightChangeList.addListener(listener);
}
public void removeListener(ChangeList.Listener listener) {
myBaseToLeftChangeList.removeListener(listener);
myBaseToRightChangeList.removeListener(listener);
}
private void addActions(@NotNull final FragmentSide side) {
ChangeList changeList = getChanges(side);
final FragmentSide originalSide = BRANCH_SIDE;
for (int i = 0; i < changeList.getCount(); i++) {
final Change change = changeList.getChange(i);
if (!change.canHasActions(originalSide)) continue;
AnAction applyAction = new AnAction(DiffBundle.message("merge.dialog.apply.change.action.name"), null, AllIcons.Diff.Arrow) {
public void actionPerformed(AnActionEvent e) {
apply(change);
}
};
AnAction ignoreAction = new AnAction(DiffBundle.message("merge.dialog.ignore.change.action.name"), null, AllIcons.Diff.Remove) {
public void actionPerformed(AnActionEvent e) {
change.removeFromList();
}
};
change.getChangeSide(originalSide).getHighlighterHolder().setActions(new AnAction[]{applyAction, ignoreAction});
}
}
private static void apply(final Change change) {
Change.apply(change, BRANCH_SIDE);
}
@NotNull
public ChangeList getChanges(@NotNull final FragmentSide changesSide) {
if (changesSide == FragmentSide.SIDE1) {
return myBaseToLeftChangeList;
}
else {
return myBaseToRightChangeList;
}
}
public void removeChanges(@Nullable Change leftChange, @Nullable Change rightChange) {
if (leftChange != null) {
myBaseToLeftChangeList.remove(leftChange);
}
if (rightChange != null) {
myBaseToRightChangeList.remove(rightChange);
}
}
public Document getBaseDocument() {
Document document = myBaseToLeftChangeList.getDocument(BASE_SIDE);
LOG.assertTrue(document == myBaseToRightChangeList.getDocument(BASE_SIDE));
return document;
}
@Nullable
public static MergeList fromDataContext(DataContext dataContext) {
MergeList mergeList = DATA_KEY.getData(dataContext);
if (mergeList != null) return mergeList;
MergePanel2 mergePanel = MergePanel2.fromDataContext(dataContext);
return mergePanel == null ? null : mergePanel.getMergeList();
}
public <T> T getUserData(@NotNull Key<T> key) {
return myDataHolder.getUserData(key);
}
public <T> void putUserData(@NotNull Key<T> key, T value) {
myDataHolder.putUserData(key, value);
}
@NotNull
public FragmentSide getSideOf(@NotNull ChangeList source) {
if (myBaseToLeftChangeList == source) {
return FragmentSide.SIDE1;
}
else {
return FragmentSide.SIDE2;
}
}
public void updateMarkup() {
myBaseToLeftChangeList.updateMarkup();
myBaseToRightChangeList.updateMarkup();
}
@Nullable
public String getErrorMessage() {
return myErrorMessage;
}
}