| /* |
| * 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; |
| } |
| } |
| } |