diff: do not show 'contents identical' if we can't calculate diff
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / diff / impl / highlighting / FragmentedDiffPanelState.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.diff.impl.highlighting;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.diff.DiffContent;
20 import com.intellij.openapi.diff.impl.ContentChangeListener;
21 import com.intellij.openapi.diff.impl.fragments.FragmentListImpl;
22 import com.intellij.openapi.diff.impl.fragments.LineFragment;
23 import com.intellij.openapi.diff.impl.processing.DiffPolicy;
24 import com.intellij.openapi.diff.impl.processing.TextCompareProcessor;
25 import com.intellij.openapi.diff.impl.splitter.LineBlocks;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.editor.LogicalPosition;
28 import com.intellij.openapi.editor.markup.TextAttributes;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.TextRange;
31 import com.intellij.util.BeforeAfter;
32 import com.intellij.util.Consumer;
33 import com.intellij.util.diff.FilesTooBigForDiffException;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import javax.swing.*;
38 import java.awt.*;
39 import java.util.*;
40 import java.util.List;
41
42 /**
43  * for read-only documents..
44  *
45  * @author irengrig
46  *         Date: 7/6/11
47  *         Time: 7:31 PM
48  */
49 public class FragmentedDiffPanelState extends DiffPanelState {
50   // fragment _start_ lines
51   private List<BeforeAfter<Integer>> myRanges;
52   private final NumberedFragmentHighlighter myFragmentHighlighter;
53   private FragmentSeparatorsPositionConsumer mySeparatorsPositionConsumer;
54
55   public FragmentedDiffPanelState(ContentChangeListener changeListener, Project project, boolean drawNumber, @NotNull Disposable parentDisposable) {
56     super(changeListener, project, parentDisposable);
57     myFragmentHighlighter = new NumberedFragmentHighlighter(myAppender1, myAppender2, drawNumber);
58     mySeparatorsPositionConsumer = new FragmentSeparatorsPositionConsumer();
59   }
60
61   @Override
62   public void setContents(DiffContent content1, DiffContent content2) {
63     myAppender1.setContent(content1);
64     myAppender2.setContent(content2);
65     myFragmentHighlighter.reset();
66   }
67
68   private LineBlocks addMarkup(final List<LineFragment> lines) {
69     myFragmentHighlighter.precalculateNumbers(lines);
70
71     for (Iterator<LineFragment> iterator = lines.iterator(); iterator.hasNext();) {
72       LineFragment line = iterator.next();
73       myFragmentHighlighter.setIsLast(!iterator.hasNext());
74       line.highlight(myFragmentHighlighter);
75     }
76     ArrayList<LineFragment> allLineFragments = new ArrayList<LineFragment>();
77     for (LineFragment lineFragment : lines) {
78       allLineFragments.add(lineFragment);
79       lineFragment.addAllDescendantsTo(allLineFragments);
80     }
81     myFragmentList = FragmentListImpl.fromList(allLineFragments);
82     return LineBlocks.fromLineFragments(allLineFragments);
83   }
84
85   public void addRangeHighlighter(final boolean left, int start, int end, final TextAttributes attributes) {
86     myFragmentHighlighter.addRangeHighlighter(left, start, end, attributes);
87   }
88
89   private void resetMarkup() {
90     myAppender1.resetHighlighters();
91     myAppender2.resetHighlighters();
92   }
93
94   @Nullable
95   public LineBlocks updateEditors() throws FilesTooBigForDiffException {
96     resetMarkup();
97     mySeparatorsPositionConsumer.clear();
98     if (myAppender1.getEditor() == null || myAppender2.getEditor() == null) {
99       return null;
100     }
101
102     int previousBefore = -1;
103     int previousAfter = -1;
104
105     final List<BeforeAfter<TextRange>> ranges = new ArrayList<BeforeAfter<TextRange>>();
106     for (int i = 0; i < myRanges.size(); i++) {
107       final BeforeAfter<Integer> start = lineStarts(i);
108       final BeforeAfter<Integer> end = i == myRanges.size() - 1 ?
109         new BeforeAfter<Integer>(myAppender1.getDocument().getTextLength(), myAppender2.getDocument().getTextLength()) :
110         lineStarts(i + 1);
111
112       ranges.add(new BeforeAfter<TextRange>(new TextRange(start.getBefore(), end.getBefore()),
113                                             new TextRange(start.getAfter(), end.getAfter())));
114
115       if (previousBefore > 0 && previousAfter > 0) {
116         final int finalPreviousBefore = previousBefore;
117         mySeparatorsPositionConsumer.prepare(previousBefore, previousAfter);
118
119         myAppender1.setSeparatorMarker(previousBefore, new Consumer<Integer>() {
120           @Override
121           public void consume(Integer integer) {
122             mySeparatorsPositionConsumer.addLeft(finalPreviousBefore, integer);
123           }
124         });
125         final int finalPreviousAfter = previousAfter;
126         myAppender2.setSeparatorMarker(previousAfter, new Consumer<Integer>() {
127           @Override
128           public void consume(Integer integer) {
129             mySeparatorsPositionConsumer.addRight(finalPreviousAfter, integer);
130           }
131         });
132       }
133       previousBefore = myRanges.get(i).getBefore();
134       previousAfter = myRanges.get(i).getAfter();
135     }
136
137     final PresetBlocksDiffPolicy diffPolicy = new PresetBlocksDiffPolicy(DiffPolicy.LINES_WO_FORMATTING);
138     // shouldn't be set since component is reused. or no getDiffPolicy for delegate initialization
139     //setDiffPolicy(diffPolicy);
140     diffPolicy.setRanges(ranges);
141
142     return addMarkup(
143       new TextCompareProcessor(myComparisonPolicy, diffPolicy, myHighlightMode).process(myAppender1.getText(), myAppender2.getText()));
144   }
145
146   private BeforeAfter<Integer> lineStarts(int i) {
147     return new BeforeAfter<Integer>(myAppender1.getDocument().getLineStartOffset(myRanges.get(i).getBefore()),
148       myAppender2.getDocument().getLineStartOffset(myRanges.get(i).getAfter()));
149   }
150
151   public void setRanges(List<BeforeAfter<Integer>> ranges) {
152     myRanges = new ArrayList<BeforeAfter<Integer>>();
153     if (! ranges.isEmpty()) {
154       if (ranges.get(0).getAfter() != 0 && ranges.get(0).getBefore() != 0) {
155         myRanges.add(new BeforeAfter<Integer>(0,0));
156       }
157     }
158     myRanges.addAll(ranges);
159   }
160   
161   public List<Integer> getLeftLines() {
162     return myFragmentHighlighter.getLeftLines();
163   }
164
165   public List<Integer> getRightLines() {
166     return myFragmentHighlighter.getRightLines();
167   }
168
169   public FragmentSeparatorsPositionConsumer getSeparatorsPositionConsumer() {
170     return mySeparatorsPositionConsumer;
171   }
172
173   @Override
174   public void drawOnDivider(Graphics gr, JComponent component) {
175     if (myAppender1.getEditor() == null || myAppender2.getEditor() == null) return;
176
177     final int startLeft = getStartVisibleLine(myAppender1.getEditor());
178     final int startRight = getStartVisibleLine(myAppender2.getEditor());
179
180     final int width = component.getWidth();
181     final int lineHeight = myAppender1.getEditor().getLineHeight();
182
183     final TreeMap<Integer, FragmentSeparatorsPositionConsumer.TornSeparator> left = mySeparatorsPositionConsumer.getLeft();
184
185     final int leftScrollOffset = myAppender1.getEditor().getScrollingModel().getVerticalScrollOffset();
186     final int rightScrollOffset = myAppender2.getEditor().getScrollingModel().getVerticalScrollOffset();
187
188     final Graphics g = gr.create();
189
190     try {
191       ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
192       for (Map.Entry<Integer, FragmentSeparatorsPositionConsumer.TornSeparator> entry : left.entrySet()) {
193         final FragmentSeparatorsPositionConsumer.TornSeparator tornSeparator = entry.getValue();
194         if (tornSeparator.getLeftLine() >= startLeft || tornSeparator.getRightLine() >= startRight) {
195           final int leftOffset = tornSeparator.getLeftOffset();
196           int leftBaseY = myAppender1.getEditor().logicalPositionToXY(new LogicalPosition(tornSeparator.getLeftLine(), 0)).y - lineHeight/2 -
197                           leftScrollOffset;
198
199           final int rightOffset = tornSeparator.getRightOffset();
200           int rightBaseY = myAppender2.getEditor().logicalPositionToXY(new LogicalPosition(tornSeparator.getRightLine(), 0)).y - lineHeight/2 -
201                            rightScrollOffset;
202
203           g.setColor(FragmentBoundRenderer.darkerBorder());
204           g.drawLine(0,leftBaseY + leftOffset + TornLineParams.ourDark - 1, width, rightBaseY + rightOffset + TornLineParams.ourDark + 1);
205           g.drawLine(0,leftBaseY + leftOffset + TornLineParams.ourDark + 1, width, rightBaseY + rightOffset + TornLineParams.ourDark - 1);
206           g.drawLine(0,leftBaseY + leftOffset - TornLineParams.ourDark - 1, width, rightBaseY + rightOffset - TornLineParams.ourDark + 1);
207           g.drawLine(0,leftBaseY + leftOffset - TornLineParams.ourDark + 1, width, rightBaseY + rightOffset - TornLineParams.ourDark - 1);
208
209           g.setColor(FragmentBoundRenderer.darkerBorder().darker());
210           // +- 2
211           g.drawLine(0,leftBaseY + leftOffset + TornLineParams.ourDark, width, rightBaseY + rightOffset + TornLineParams.ourDark);
212           g.drawLine(0, leftBaseY + leftOffset - TornLineParams.ourDark, width, rightBaseY + rightOffset - TornLineParams.ourDark);
213         }
214       }
215     } finally {
216       g.dispose();
217     }
218   }
219
220   private int getStartVisibleLine(final Editor editor) {
221     int offset = editor.getScrollingModel().getVerticalScrollOffset();
222      LogicalPosition logicalPosition = editor.xyToLogicalPosition(new Point(0, offset));
223      return logicalPosition.line;
224   }
225 }