7e395c1b4a9ecb0eabb822bd69fff5f34b66a590
[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
36 import javax.swing.*;
37 import java.awt.*;
38 import java.util.*;
39 import java.util.List;
40
41 /**
42  * for read-only documents..
43  *
44  * @author irengrig
45  *         Date: 7/6/11
46  *         Time: 7:31 PM
47  */
48 public class FragmentedDiffPanelState extends DiffPanelState {
49   // fragment _start_ lines
50   private List<BeforeAfter<Integer>> myRanges;
51   private final NumberedFragmentHighlighter myFragmentHighlighter;
52   private FragmentSeparatorsPositionConsumer mySeparatorsPositionConsumer;
53
54   public FragmentedDiffPanelState(ContentChangeListener changeListener, Project project, boolean drawNumber, @NotNull Disposable parentDisposable) {
55     super(changeListener, project, parentDisposable);
56     myFragmentHighlighter = new NumberedFragmentHighlighter(myAppender1, myAppender2, drawNumber);
57     mySeparatorsPositionConsumer = new FragmentSeparatorsPositionConsumer();
58   }
59
60   @Override
61   public void setContents(DiffContent content1, DiffContent content2) {
62     myAppender1.setContent(content1);
63     myAppender2.setContent(content2);
64     myFragmentHighlighter.reset();
65   }
66
67   private LineBlocks addMarkup(final List<LineFragment> lines) {
68     myFragmentHighlighter.precalculateNumbers(lines);
69
70     for (Iterator<LineFragment> iterator = lines.iterator(); iterator.hasNext();) {
71       LineFragment line = iterator.next();
72       myFragmentHighlighter.setIsLast(!iterator.hasNext());
73       line.highlight(myFragmentHighlighter);
74     }
75     ArrayList<LineFragment> allLineFragments = new ArrayList<LineFragment>();
76     for (LineFragment lineFragment : lines) {
77       allLineFragments.add(lineFragment);
78       lineFragment.addAllDescendantsTo(allLineFragments);
79     }
80     myFragmentList = FragmentListImpl.fromList(allLineFragments);
81     return LineBlocks.fromLineFragments(allLineFragments);
82   }
83
84   public void addRangeHighlighter(final boolean left, int start, int end, final TextAttributes attributes) {
85     myFragmentHighlighter.addRangeHighlighter(left, start, end, attributes);
86   }
87
88   private void resetMarkup() {
89     myAppender1.resetHighlighters();
90     myAppender2.resetHighlighters();
91   }
92
93   public LineBlocks updateEditors() throws FilesTooBigForDiffException {
94     resetMarkup();
95     mySeparatorsPositionConsumer.clear();
96     if (myAppender1.getEditor() == null || myAppender2.getEditor() == null) {
97       return LineBlocks.EMPTY;
98     }
99
100     int previousBefore = -1;
101     int previousAfter = -1;
102
103     final List<BeforeAfter<TextRange>> ranges = new ArrayList<BeforeAfter<TextRange>>();
104     for (int i = 0; i < myRanges.size(); i++) {
105       final BeforeAfter<Integer> start = lineStarts(i);
106       final BeforeAfter<Integer> end = i == myRanges.size() - 1 ?
107         new BeforeAfter<Integer>(myAppender1.getDocument().getTextLength(), myAppender2.getDocument().getTextLength()) :
108         lineStarts(i + 1);
109
110       ranges.add(new BeforeAfter<TextRange>(new TextRange(start.getBefore(), end.getBefore()),
111                                             new TextRange(start.getAfter(), end.getAfter())));
112
113       if (previousBefore > 0 && previousAfter > 0) {
114         final int finalPreviousBefore = previousBefore;
115         mySeparatorsPositionConsumer.prepare(previousBefore, previousAfter);
116
117         myAppender1.setSeparatorMarker(previousBefore, new Consumer<Integer>() {
118           @Override
119           public void consume(Integer integer) {
120             mySeparatorsPositionConsumer.addLeft(finalPreviousBefore, integer);
121           }
122         });
123         final int finalPreviousAfter = previousAfter;
124         myAppender2.setSeparatorMarker(previousAfter, new Consumer<Integer>() {
125           @Override
126           public void consume(Integer integer) {
127             mySeparatorsPositionConsumer.addRight(finalPreviousAfter, integer);
128           }
129         });
130       }
131       previousBefore = myRanges.get(i).getBefore();
132       previousAfter = myRanges.get(i).getAfter();
133     }
134
135     final PresetBlocksDiffPolicy diffPolicy = new PresetBlocksDiffPolicy(DiffPolicy.LINES_WO_FORMATTING);
136     // shouldn't be set since component is reused. or no getDiffPolicy for delegate initialization
137     //setDiffPolicy(diffPolicy);
138     diffPolicy.setRanges(ranges);
139
140     return addMarkup(
141       new TextCompareProcessor(myComparisonPolicy, diffPolicy, myHighlightMode).process(myAppender1.getText(), myAppender2.getText()));
142   }
143
144   private BeforeAfter<Integer> lineStarts(int i) {
145     return new BeforeAfter<Integer>(myAppender1.getDocument().getLineStartOffset(myRanges.get(i).getBefore()),
146       myAppender2.getDocument().getLineStartOffset(myRanges.get(i).getAfter()));
147   }
148
149   public void setRanges(List<BeforeAfter<Integer>> ranges) {
150     myRanges = new ArrayList<BeforeAfter<Integer>>();
151     if (! ranges.isEmpty()) {
152       if (ranges.get(0).getAfter() != 0 && ranges.get(0).getBefore() != 0) {
153         myRanges.add(new BeforeAfter<Integer>(0,0));
154       }
155     }
156     myRanges.addAll(ranges);
157   }
158   
159   public List<Integer> getLeftLines() {
160     return myFragmentHighlighter.getLeftLines();
161   }
162
163   public List<Integer> getRightLines() {
164     return myFragmentHighlighter.getRightLines();
165   }
166
167   public FragmentSeparatorsPositionConsumer getSeparatorsPositionConsumer() {
168     return mySeparatorsPositionConsumer;
169   }
170
171   @Override
172   public void drawOnDivider(Graphics gr, JComponent component) {
173     if (myAppender1.getEditor() == null || myAppender2.getEditor() == null) return;
174
175     final int startLeft = getStartVisibleLine(myAppender1.getEditor());
176     final int startRight = getStartVisibleLine(myAppender2.getEditor());
177
178     final int width = component.getWidth();
179     final int lineHeight = myAppender1.getEditor().getLineHeight();
180
181     final TreeMap<Integer, FragmentSeparatorsPositionConsumer.TornSeparator> left = mySeparatorsPositionConsumer.getLeft();
182
183     final int leftScrollOffset = myAppender1.getEditor().getScrollingModel().getVerticalScrollOffset();
184     final int rightScrollOffset = myAppender2.getEditor().getScrollingModel().getVerticalScrollOffset();
185
186     final Graphics g = gr.create();
187
188     try {
189       ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
190       for (Map.Entry<Integer, FragmentSeparatorsPositionConsumer.TornSeparator> entry : left.entrySet()) {
191         final FragmentSeparatorsPositionConsumer.TornSeparator tornSeparator = entry.getValue();
192         if (tornSeparator.getLeftLine() >= startLeft || tornSeparator.getRightLine() >= startRight) {
193           final int leftOffset = tornSeparator.getLeftOffset();
194           int leftBaseY = myAppender1.getEditor().logicalPositionToXY(new LogicalPosition(tornSeparator.getLeftLine(), 0)).y - lineHeight/2 -
195                           leftScrollOffset;
196
197           final int rightOffset = tornSeparator.getRightOffset();
198           int rightBaseY = myAppender2.getEditor().logicalPositionToXY(new LogicalPosition(tornSeparator.getRightLine(), 0)).y - lineHeight/2 -
199                            rightScrollOffset;
200
201           g.setColor(FragmentBoundRenderer.darkerBorder());
202           g.drawLine(0,leftBaseY + leftOffset + TornLineParams.ourDark - 1, width, rightBaseY + rightOffset + TornLineParams.ourDark + 1);
203           g.drawLine(0,leftBaseY + leftOffset + TornLineParams.ourDark + 1, width, rightBaseY + rightOffset + TornLineParams.ourDark - 1);
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
207           g.setColor(FragmentBoundRenderer.darkerBorder().darker());
208           // +- 2
209           g.drawLine(0,leftBaseY + leftOffset + TornLineParams.ourDark, width, rightBaseY + rightOffset + TornLineParams.ourDark);
210           g.drawLine(0, leftBaseY + leftOffset - TornLineParams.ourDark, width, rightBaseY + rightOffset - TornLineParams.ourDark);
211         }
212       }
213     } finally {
214       g.dispose();
215     }
216   }
217
218   private int getStartVisibleLine(final Editor editor) {
219     int offset = editor.getScrollingModel().getVerticalScrollOffset();
220      LogicalPosition logicalPosition = editor.xyToLogicalPosition(new Point(0, offset));
221      return logicalPosition.line;
222   }
223 }