blob: f83bd64ed738e9234c4611dd2440caffa78c2f7c [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.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.diff.impl.util.DocumentUtil;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Comparator;
/**
* Represents a change in diff or merge view.
* A change has two {@link com.intellij.openapi.diff.impl.incrementalMerge.Change.SimpleChangeSide sides} (left and right), each of them representing the text which has been changed and the original text
* shown in the diff/merge.
* Change can be applied, then its sides would be equal.
*/
public abstract class Change {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.incrementalMerge.Change");
public abstract ChangeSide getChangeSide(FragmentSide side);
public abstract ChangeType getType();
public abstract ChangeList getChangeList();
protected abstract void removeFromList();
/**
* Called when a change has been applied.
*/
public abstract void onApplied();
/**
* Called when a change has been removed from the list.
*/
public abstract void onRemovedFromList();
public abstract boolean isValid();
/**
* Apply the change, i.e. change the "Merge result" document and update range markers, highlighting, gutters, etc.
* @param source The source side of the change, which is being applied.
*/
private void apply(@NotNull FragmentSide original) {
FragmentSide targetSide = original.otherSide();
RangeMarker originalRangeMarker = getRangeMarker(original);
RangeMarker rangeMarker = getRangeMarker(targetSide);
if (originalRangeMarker != null && rangeMarker != null) {
TextRange textRange = modifyDocument(getProject(), originalRangeMarker, rangeMarker);
if (textRange != null && isValid()) {
updateTargetRangeMarker(targetSide, textRange);
}
onApplied();
}
}
/**
* Updates the target marker of a change after the change has been applied
* to allow highlighting of the document modification which has been performed.
* @param targetFragmentSide The side to be changed.
* @param updatedTextRange New text range to be applied to the side.
*/
protected final void updateTargetRangeMarker(@NotNull FragmentSide targetFragmentSide, @NotNull TextRange updatedTextRange) {
ChangeSide targetSide = getChangeSide(targetFragmentSide);
DiffRangeMarker originalRange = targetSide.getRange();
DiffRangeMarker updatedRange = new DiffRangeMarker(originalRange.getDocument(), updatedTextRange, null);
changeSide(targetSide, updatedRange);
}
/**
* Substitutes the specified side of this change to a new side which contains the given range.
* @param sideToChange The side to be changed.
* @param newRange New text range of the new side.
*/
protected abstract void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange);
/**
* Applies the text from the original marker to the target marker.
* @return the resulting TextRange from the target document, or null if the document if not writable.
*/
@Nullable
private static TextRange modifyDocument(@NotNull Project project, @NotNull RangeMarker original, @NotNull RangeMarker target) {
Document document = target.getDocument();
if (!ReadonlyStatusHandler.ensureDocumentWritable(project, document)) { return null; }
if (DocumentUtil.isEmpty(original)) {
int offset = target.getStartOffset();
document.deleteString(offset, target.getEndOffset());
}
String text = DocumentUtil.getText(original);
int startOffset = target.getStartOffset();
if (DocumentUtil.isEmpty(target)) {
document.insertString(startOffset, text);
} else {
document.replaceString(startOffset, target.getEndOffset(), text);
}
return new TextRange(startOffset, startOffset + text.length());
}
public void addMarkup(Editor[] editors) {
LOG.assertTrue(editors.length == 2);
highlight(editors, FragmentSide.SIDE1);
highlight(editors, FragmentSide.SIDE2);
}
private void highlight(Editor[] editors, FragmentSide side) {
getHighlighterHolder(side).highlight(getChangeSide(side), editors[side.getIndex()], getType());
}
private void updateHighlighter(FragmentSide side) {
getHighlighterHolder(side).updateHighlighter(getChangeSide(side), getType());
}
private Project getProject() { return getChangeList().getProject(); }
private ChangeHighlighterHolder getHighlighterHolder(FragmentSide side) {
return getChangeSide(side).getHighlighterHolder();
}
private RangeMarker getRangeMarker(FragmentSide side) {
ChangeSide changeSide = getChangeSide(side);
LOG.assertTrue(changeSide != null);
return changeSide.getRange();
}
public static void apply(final Change change, final FragmentSide fromSide) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
CommandProcessor.getInstance().executeCommand(change.getProject(), new Runnable() {
public void run() {
change.apply(fromSide);
}
}, null, DiffBundle.message("save.merge.result.command.name"));
}
});
}
public void updateMarkup() {
updateHighlighter(FragmentSide.SIDE1);
updateHighlighter(FragmentSide.SIDE2);
}
public boolean canHasActions(FragmentSide fromSide) {
FragmentSide targetSide = fromSide.otherSide();
Document targetDocument = getChangeList().getDocument(targetSide);
if (!targetDocument.isWritable()) return false;
Editor targetEditor = getHighlighterHolder(targetSide).getEditor();
return !targetEditor.isViewer();
}
public static class ChangeOrder implements Comparator<Change> {
private final FragmentSide myMainSide;
public ChangeOrder(FragmentSide mainSide) {
myMainSide = mainSide;
}
public int compare(Change change, Change change1) {
int result1 = compareSide(change, change1, myMainSide);
if (result1 != 0) return result1;
return compareSide(change, change1, myMainSide.otherSide());
}
private static int compareSide(Change change, Change change1, FragmentSide side) {
return RangeMarker.BY_START_OFFSET.compare(change.getRangeMarker(side), change1.getRangeMarker(side));
}
}
protected static class SimpleChangeSide extends ChangeSide {
private final FragmentSide mySide;
private final DiffRangeMarker myRange;
private final ChangeHighlighterHolder myHighlighterHolder;
public SimpleChangeSide(FragmentSide side, DiffRangeMarker rangeMarker) {
mySide = side;
myRange = rangeMarker;
myHighlighterHolder = new ChangeHighlighterHolder();
}
public SimpleChangeSide(@NotNull ChangeSide originalSide, @NotNull DiffRangeMarker newRange) {
mySide = ((SimpleChangeSide)originalSide).getFragmentSide();
myRange = newRange;
myHighlighterHolder = originalSide.getHighlighterHolder();
}
@NotNull
public FragmentSide getFragmentSide() {
return mySide;
}
@NotNull
public DiffRangeMarker getRange() {
return myRange;
}
public ChangeHighlighterHolder getHighlighterHolder() {
return myHighlighterHolder;
}
}
}