/* * Copyright 2000-2011 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.highlighting; import com.intellij.openapi.Disposable; import com.intellij.openapi.diff.DiffContent; import com.intellij.openapi.diff.impl.ContentChangeListener; import com.intellij.openapi.diff.impl.fragments.FragmentListImpl; import com.intellij.openapi.diff.impl.fragments.LineFragment; import com.intellij.openapi.diff.impl.processing.DiffPolicy; import com.intellij.openapi.diff.impl.processing.TextCompareProcessor; import com.intellij.openapi.diff.impl.splitter.LineBlocks; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.util.BeforeAfter; import com.intellij.util.Consumer; import com.intellij.util.diff.FilesTooBigForDiffException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.util.*; import java.util.List; /** * for read-only documents.. * * @author irengrig * Date: 7/6/11 * Time: 7:31 PM */ public class FragmentedDiffPanelState extends DiffPanelState { // fragment _start_ lines private List<BeforeAfter<Integer>> myRanges; private final NumberedFragmentHighlighter myFragmentHighlighter; private FragmentSeparatorsPositionConsumer mySeparatorsPositionConsumer; public FragmentedDiffPanelState(ContentChangeListener changeListener, Project project, int diffDividerPolygonsOffset, boolean drawNumber, @NotNull Disposable parentDisposable) { super(changeListener, project, diffDividerPolygonsOffset, parentDisposable); myFragmentHighlighter = new NumberedFragmentHighlighter(myAppender1, myAppender2, drawNumber); mySeparatorsPositionConsumer = new FragmentSeparatorsPositionConsumer(); } @Override public void setContents(DiffContent content1, DiffContent content2) { myAppender1.setContent(content1); myAppender2.setContent(content2); myFragmentHighlighter.reset(); } private LineBlocks addMarkup(final List<LineFragment> lines) { myFragmentHighlighter.precalculateNumbers(lines); for (Iterator<LineFragment> iterator = lines.iterator(); iterator.hasNext(); ) { LineFragment line = iterator.next(); myFragmentHighlighter.setIsLast(!iterator.hasNext()); line.highlight(myFragmentHighlighter); } ArrayList<LineFragment> allLineFragments = new ArrayList<LineFragment>(); for (LineFragment lineFragment : lines) { allLineFragments.add(lineFragment); lineFragment.addAllDescendantsTo(allLineFragments); } myFragmentList = FragmentListImpl.fromList(allLineFragments); return LineBlocks.fromLineFragments(allLineFragments); } public void addRangeHighlighter(final boolean left, int start, int end, final TextAttributes attributes) { myFragmentHighlighter.addRangeHighlighter(left, start, end, attributes); } private void resetMarkup() { myAppender1.resetHighlighters(); myAppender2.resetHighlighters(); } @Nullable public LineBlocks updateEditors() throws FilesTooBigForDiffException { resetMarkup(); mySeparatorsPositionConsumer.clear(); if (myAppender1.getEditor() == null || myAppender2.getEditor() == null) { return null; } int previousBefore = -1; int previousAfter = -1; final List<BeforeAfter<TextRange>> ranges = new ArrayList<BeforeAfter<TextRange>>(); for (int i = 0; i < myRanges.size(); i++) { final BeforeAfter<Integer> start = lineStarts(i); final BeforeAfter<Integer> end = i == myRanges.size() - 1 ? new BeforeAfter<Integer>(myAppender1.getDocument().getTextLength(), myAppender2.getDocument().getTextLength()) : lineStarts(i + 1); ranges.add(new BeforeAfter<TextRange>(new TextRange(start.getBefore(), end.getBefore()), new TextRange(start.getAfter(), end.getAfter()))); if (previousBefore > 0 && previousAfter > 0) { final int finalPreviousBefore = previousBefore; mySeparatorsPositionConsumer.prepare(previousBefore, previousAfter); myAppender1.setSeparatorMarker(previousBefore, new Consumer<Integer>() { @Override public void consume(Integer integer) { mySeparatorsPositionConsumer.addLeft(finalPreviousBefore, integer); } }); final int finalPreviousAfter = previousAfter; myAppender2.setSeparatorMarker(previousAfter, new Consumer<Integer>() { @Override public void consume(Integer integer) { mySeparatorsPositionConsumer.addRight(finalPreviousAfter, integer); } }); } previousBefore = myRanges.get(i).getBefore(); previousAfter = myRanges.get(i).getAfter(); } final PresetBlocksDiffPolicy diffPolicy = new PresetBlocksDiffPolicy(DiffPolicy.LINES_WO_FORMATTING); // shouldn't be set since component is reused. or no getDiffPolicy for delegate initialization //setDiffPolicy(diffPolicy); diffPolicy.setRanges(ranges); return addMarkup( new TextCompareProcessor(myComparisonPolicy, diffPolicy, myHighlightMode).process(myAppender1.getText(), myAppender2.getText())); } private BeforeAfter<Integer> lineStarts(int i) { return new BeforeAfter<Integer>(myAppender1.getDocument().getLineStartOffset(myRanges.get(i).getBefore()), myAppender2.getDocument().getLineStartOffset(myRanges.get(i).getAfter())); } public void setRanges(List<BeforeAfter<Integer>> ranges) { myRanges = new ArrayList<BeforeAfter<Integer>>(); if (!ranges.isEmpty()) { if (ranges.get(0).getAfter() != 0 && ranges.get(0).getBefore() != 0) { myRanges.add(new BeforeAfter<Integer>(0, 0)); } } myRanges.addAll(ranges); } public List<Integer> getLeftLines() { return myFragmentHighlighter.getLeftLines(); } public List<Integer> getRightLines() { return myFragmentHighlighter.getRightLines(); } public FragmentSeparatorsPositionConsumer getSeparatorsPositionConsumer() { return mySeparatorsPositionConsumer; } @Override public void drawOnDivider(Graphics gr, JComponent component) { if (myAppender1.getEditor() == null || myAppender2.getEditor() == null) return; final int startLeft = getStartVisibleLine(myAppender1.getEditor()); final int startRight = getStartVisibleLine(myAppender2.getEditor()); final int width = component.getWidth(); final int lineHeight = myAppender1.getEditor().getLineHeight(); final TreeMap<Integer, FragmentSeparatorsPositionConsumer.TornSeparator> left = mySeparatorsPositionConsumer.getLeft(); final int leftScrollOffset = myAppender1.getEditor().getScrollingModel().getVerticalScrollOffset(); final int rightScrollOffset = myAppender2.getEditor().getScrollingModel().getVerticalScrollOffset(); final Graphics g = gr.create(); try { ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); for (Map.Entry<Integer, FragmentSeparatorsPositionConsumer.TornSeparator> entry : left.entrySet()) { final FragmentSeparatorsPositionConsumer.TornSeparator tornSeparator = entry.getValue(); if (tornSeparator.getLeftLine() >= startLeft || tornSeparator.getRightLine() >= startRight) { final int leftOffset = tornSeparator.getLeftOffset(); int leftBaseY = myAppender1.getEditor().logicalPositionToXY(new LogicalPosition(tornSeparator.getLeftLine(), 0)).y - lineHeight / 2 - leftScrollOffset + myDiffDividerPolygonsOffset; final int rightOffset = tornSeparator.getRightOffset(); int rightBaseY = myAppender2.getEditor().logicalPositionToXY(new LogicalPosition(tornSeparator.getRightLine(), 0)).y - lineHeight / 2 - rightScrollOffset + myDiffDividerPolygonsOffset; int x1 = 0; int x2 = width; int y1 = leftBaseY + leftOffset; int y2 = rightBaseY + rightOffset; if (Math.abs(x2 - x1) < Math.abs(y2 - y1)) { int dx = TornLineParams.ourDark; int dy = TornLineParams.ourLight; if (y2 < y1) { g.setColor(FragmentBoundRenderer.darkerBorder()); g.drawLine(x1 + dx, y1 - dy + TornLineParams.ourDark, x2, y2 + TornLineParams.ourDark); g.drawLine(x1, y1 - TornLineParams.ourDark, x2 - dx, y2 + dy - TornLineParams.ourDark); g.drawLine(x1, y1 + TornLineParams.ourDark, x1 + dx, y1 - dy + TornLineParams.ourDark); g.drawLine(x2, y2 - TornLineParams.ourDark, x2 - dx, y2 + dy - TornLineParams.ourDark); g.setColor(FragmentBoundRenderer.darkerBorder().darker()); g.drawLine(x1 + dx, y1 - dy + TornLineParams.ourLight, x2, y2 + TornLineParams.ourLight); g.drawLine(x1, y1 - TornLineParams.ourLight, x2 - dx, y2 + dy - TornLineParams.ourLight); g.drawLine(x1, y1 + TornLineParams.ourLight, x1 + dx, y1 - dy + TornLineParams.ourLight); g.drawLine(x2, y2 - TornLineParams.ourLight, x2 - dx, y2 + dy - TornLineParams.ourLight); } else { g.setColor(FragmentBoundRenderer.darkerBorder()); g.drawLine(x1, y1 + TornLineParams.ourDark, x2 - dx, y2 - dy + TornLineParams.ourDark); g.drawLine(x1 + dx, y1 + dy - TornLineParams.ourDark, x2, y2 - TornLineParams.ourDark); g.drawLine(x2, y2 + TornLineParams.ourDark, x2 - dx, y2 - dy + TornLineParams.ourDark); g.drawLine(x1, y1 - TornLineParams.ourDark, x1 + dx, y1 + dy - TornLineParams.ourDark); g.setColor(FragmentBoundRenderer.darkerBorder().darker()); g.drawLine(x1, y1 + TornLineParams.ourLight, x2 - dx, y2 - dy + TornLineParams.ourLight); g.drawLine(x1 + dx, y1 + dy - TornLineParams.ourLight, x2, y2 - TornLineParams.ourLight); g.drawLine(x2, y2 + TornLineParams.ourLight, x2 - dx, y2 - dy + TornLineParams.ourLight); g.drawLine(x1, y1 - TornLineParams.ourLight, x1 + dx, y1 + dy - TornLineParams.ourLight); } } else { g.setColor(FragmentBoundRenderer.darkerBorder()); g.drawLine(x1, y1 + TornLineParams.ourDark, x2, y2 + TornLineParams.ourDark); g.drawLine(x1, y1 - TornLineParams.ourDark, x2, y2 - TornLineParams.ourDark); g.setColor(FragmentBoundRenderer.darkerBorder().darker()); g.drawLine(x1, y1 + TornLineParams.ourLight, x2, y2 + TornLineParams.ourLight); g.drawLine(x1, y1 - TornLineParams.ourLight, x2, y2 - TornLineParams.ourLight); } } } } finally { g.dispose(); } } private int getStartVisibleLine(final Editor editor) { int offset = editor.getScrollingModel().getVerticalScrollOffset(); LogicalPosition logicalPosition = editor.xyToLogicalPosition(new Point(0, offset)); return logicalPosition.line; } }