/* * Copyright 2000-2015 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.diff.tools.simple; import com.intellij.diff.fragments.DiffFragment; import com.intellij.diff.fragments.LineFragment; import com.intellij.diff.util.*; import com.intellij.internal.statistic.UsageTrigger; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.openapi.editor.markup.HighlighterLayer; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; import java.util.List; public class SimpleDiffChange { @NotNull private final SimpleDiffViewer myViewer; @NotNull private final LineFragment myFragment; @Nullable private final List<DiffFragment> myInnerFragments; @NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<>(); @NotNull private final List<MyGutterOperation> myOperations = new ArrayList<>(); private boolean myIsValid = true; private int[] myLineStartShifts = new int[2]; private int[] myLineEndShifts = new int[2]; public SimpleDiffChange(@NotNull SimpleDiffViewer viewer, @NotNull LineFragment fragment, @Nullable LineFragment previousFragment) { myViewer = viewer; myFragment = fragment; myInnerFragments = fragment.getInnerFragments(); installHighlighter(previousFragment); } public void installHighlighter(@Nullable LineFragment previousFragment) { assert myHighlighters.isEmpty(); if (myInnerFragments != null) { doInstallHighlighterWithInner(); } else { doInstallHighlighterSimple(); } doInstallNonSquashedChangesSeparator(previousFragment); doInstallActionHighlighters(); } public void destroyHighlighter() { for (RangeHighlighter highlighter : myHighlighters) { highlighter.dispose(); } myHighlighters.clear(); for (MyGutterOperation operation : myOperations) { operation.dispose(); } myOperations.clear(); } private void doInstallHighlighterSimple() { createHighlighter(Side.LEFT, false); createHighlighter(Side.RIGHT, false); } private void doInstallHighlighterWithInner() { assert myInnerFragments != null; createHighlighter(Side.LEFT, true); createHighlighter(Side.RIGHT, true); for (DiffFragment fragment : myInnerFragments) { createInlineHighlighter(fragment, Side.LEFT); createInlineHighlighter(fragment, Side.RIGHT); } } private void doInstallNonSquashedChangesSeparator(@Nullable LineFragment previousFragment) { createNonSquashedChangesSeparator(previousFragment, Side.LEFT); createNonSquashedChangesSeparator(previousFragment, Side.RIGHT); } private void doInstallActionHighlighters() { myOperations.add(createOperation(Side.LEFT)); myOperations.add(createOperation(Side.RIGHT)); } private void createHighlighter(@NotNull Side side, boolean ignored) { Editor editor = myViewer.getEditor(side); TextDiffType type = DiffUtil.getLineDiffType(myFragment); int startLine = side.getStartLine(myFragment); int endLine = side.getEndLine(myFragment); myHighlighters.addAll(DiffDrawUtil.createHighlighter(editor, startLine, endLine, type, ignored)); } private void createInlineHighlighter(@NotNull DiffFragment fragment, @NotNull Side side) { int start = side.getStartOffset(fragment); int end = side.getEndOffset(fragment); TextDiffType type = DiffUtil.getDiffType(fragment); int startOffset = side.getStartOffset(myFragment); start += startOffset; end += startOffset; Editor editor = myViewer.getEditor(side); myHighlighters.addAll(DiffDrawUtil.createInlineHighlighter(editor, start, end, type)); } private void createNonSquashedChangesSeparator(@Nullable LineFragment previousFragment, @NotNull Side side) { if (previousFragment == null) return; int startLine = side.getStartLine(myFragment); int endLine = side.getEndLine(myFragment); int prevStartLine = side.getStartLine(previousFragment); int prevEndLine = side.getEndLine(previousFragment); if (startLine == endLine) return; if (prevStartLine == prevEndLine) return; if (prevEndLine != startLine) return; myHighlighters.addAll(DiffDrawUtil.createLineMarker(myViewer.getEditor(side), startLine, TextDiffType.MODIFIED)); } public void updateGutterActions(boolean force) { for (MyGutterOperation operation : myOperations) { operation.update(force); } } // // Getters // public int getStartLine(@NotNull Side side) { return side.getStartLine(myFragment) + side.select(myLineStartShifts); } public int getEndLine(@NotNull Side side) { return side.getEndLine(myFragment) + side.select(myLineEndShifts); } @NotNull public TextDiffType getDiffType() { return DiffUtil.getLineDiffType(myFragment); } public boolean isValid() { return myIsValid; } // // Shift // public boolean processChange(int oldLine1, int oldLine2, int shift, @NotNull Side side) { int line1 = getStartLine(side); int line2 = getEndLine(side); int sideIndex = side.getIndex(); DiffUtil.UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(line1, line2, oldLine1, oldLine2, shift); myLineStartShifts[sideIndex] += newRange.startLine - line1; myLineEndShifts[sideIndex] += newRange.endLine - line2; if (newRange.damaged) { for (MyGutterOperation operation : myOperations) { operation.dispose(); } myOperations.clear(); myIsValid = false; } return newRange.damaged; } // // Change applying // public boolean isSelectedByLine(int line, @NotNull Side side) { int line1 = getStartLine(side); int line2 = getEndLine(side); return DiffUtil.isSelectedByLine(line, line1, line2); } // // Helpers // @NotNull private MyGutterOperation createOperation(@NotNull Side side) { int offset = side.getStartOffset(myFragment); EditorEx editor = myViewer.getEditor(side); RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(offset, offset, HighlighterLayer.ADDITIONAL_SYNTAX, null, HighlighterTargetArea.LINES_IN_RANGE); return new MyGutterOperation(side, highlighter); } private class MyGutterOperation { @NotNull private final Side mySide; @NotNull private final RangeHighlighter myHighlighter; private boolean myCtrlPressed; private MyGutterOperation(@NotNull Side side, @NotNull RangeHighlighter highlighter) { mySide = side; myHighlighter = highlighter; update(true); } public void dispose() { myHighlighter.dispose(); } public void update(boolean force) { if (!force && !areModifiersChanged()) { return; } if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer()); } private boolean areModifiersChanged() { return myCtrlPressed != myViewer.getModifierProvider().isCtrlPressed(); } @Nullable public GutterIconRenderer createRenderer() { myCtrlPressed = myViewer.getModifierProvider().isCtrlPressed(); boolean isOtherEditable = DiffUtil.isEditable(myViewer.getEditor(mySide.other())); boolean isAppendable = myFragment.getStartLine1() != myFragment.getEndLine1() && myFragment.getStartLine2() != myFragment.getEndLine2(); if (isOtherEditable) { if (myCtrlPressed && isAppendable) { return createAppendRenderer(mySide); } else { return createApplyRenderer(mySide); } } return null; } } @Nullable private GutterIconRenderer createApplyRenderer(@NotNull final Side side) { return createIconRenderer(side, "Accept", DiffUtil.getArrowIcon(side), () -> { myViewer.replaceChange(this, side); }); } @Nullable private GutterIconRenderer createAppendRenderer(@NotNull final Side side) { return createIconRenderer(side, "Append", DiffUtil.getArrowDownIcon(side), () -> { UsageTrigger.trigger("diff.SimpleDiffChange.Append"); myViewer.appendChange(this, side); }); } @Nullable private GutterIconRenderer createIconRenderer(@NotNull final Side sourceSide, @NotNull final String tooltipText, @NotNull final Icon icon, @NotNull final Runnable perform) { if (!DiffUtil.isEditable(myViewer.getEditor(sourceSide.other()))) return null; return new DiffGutterRenderer(icon, tooltipText) { @Override protected void performAction(AnActionEvent e) { if (!myIsValid) return; final Project project = e.getProject(); final Document document = myViewer.getEditor(sourceSide.other()).getDocument(); DiffUtil.executeWriteCommand(document, project, "Replace change", perform); } }; } }