Merge branch 'master' into new-merge
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / util / DiffDrawUtil.java
1 /*
2  * Copyright 2000-2015 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.diff.util;
17
18 import com.intellij.openapi.editor.Editor;
19 import com.intellij.openapi.editor.colors.EditorColors;
20 import com.intellij.openapi.editor.colors.EditorColorsManager;
21 import com.intellij.openapi.editor.colors.EditorColorsScheme;
22 import com.intellij.openapi.editor.markup.*;
23 import com.intellij.openapi.util.BooleanGetter;
24 import com.intellij.ui.JBColor;
25 import com.intellij.util.DocumentUtil;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.ui.UIUtil;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.awt.*;
32 import java.awt.geom.CubicCurve2D;
33 import java.awt.geom.Path2D;
34 import java.util.Collections;
35 import java.util.List;
36
37 public class DiffDrawUtil {
38   private static final int STRIPE_LAYER = HighlighterLayer.ERROR - 1;
39   private static final int DEFAULT_LAYER = HighlighterLayer.SELECTION - 3;
40   private static final int INLINE_LAYER = HighlighterLayer.SELECTION - 2;
41   private static final int LINE_MARKER_LAYER = HighlighterLayer.SELECTION - 1;
42
43   private DiffDrawUtil() {
44   }
45
46   @NotNull
47   public static Color getDividerColor() {
48     return getDividerColor(null);
49   }
50
51   @NotNull
52   public static Color getDividerColor(@Nullable Editor editor) {
53     return getDividerColorFromScheme(editor != null ? editor.getColorsScheme() : EditorColorsManager.getInstance().getGlobalScheme());
54   }
55
56   @NotNull
57   public static Color getDividerColorFromScheme(@NotNull EditorColorsScheme scheme) {
58     Color gutterBackground = scheme.getColor(EditorColors.GUTTER_BACKGROUND);
59     if (gutterBackground == null) {
60       gutterBackground = EditorColors.GUTTER_BACKGROUND.getDefaultColor();
61     }
62     return gutterBackground;
63   }
64
65   public static void drawConnectorLineSeparator(@NotNull Graphics2D g,
66                                                 int x1, int x2,
67                                                 int start1, int end1,
68                                                 int start2, int end2) {
69     drawConnectorLineSeparator(g, x1, x2, start1, end1, start2, end2, null);
70   }
71
72   public static void drawConnectorLineSeparator(@NotNull Graphics2D g,
73                                                 int x1, int x2,
74                                                 int start1, int end1,
75                                                 int start2, int end2,
76                                                 @Nullable EditorColorsScheme scheme) {
77     DiffLineSeparatorRenderer.drawConnectorLine(g, x1, x2, start1, start2, end1 - start1, scheme);
78   }
79
80   public static void drawDoubleChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
81     UIUtil.drawLine(g, x1, y, x2, y, null, color);
82     UIUtil.drawLine(g, x1, y + 1, x2, y + 1, null, color);
83   }
84
85   public static void drawChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
86     UIUtil.drawLine(g, x1, y, x2, y, null, color);
87   }
88
89   public static void drawDottedDoubleChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
90     UIUtil.drawBoldDottedLine(g, x1, x2, y - 1, null, color, false);
91     UIUtil.drawBoldDottedLine(g, x1, x2, y, null, color, false);
92   }
93
94   public static void drawDottedChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
95     UIUtil.drawBoldDottedLine(g, x1, x2, y - 1, null, color, false);
96   }
97
98   public static void drawChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color,
99                                          boolean doubleLine, boolean dottedLine) {
100     if (dottedLine && doubleLine) {
101       drawDottedDoubleChunkBorderLine(g, x1, x2, y, color);
102     }
103     else if (dottedLine) {
104       drawDottedChunkBorderLine(g, x1, x2, y, color);
105     }
106     else if (doubleLine) {
107       drawDoubleChunkBorderLine(g, x1, x2, y, color);
108     }
109     else {
110       drawChunkBorderLine(g, x1, x2, y, color);
111     }
112   }
113
114   public static void drawTrapezium(@NotNull Graphics2D g,
115                                    int x1, int x2,
116                                    int start1, int end1,
117                                    int start2, int end2,
118                                    @NotNull Color color) {
119     drawTrapezium(g, x1, x2, start1, end1, start2, end2, color, null);
120   }
121
122   public static void drawTrapezium(@NotNull Graphics2D g,
123                                    int x1, int x2,
124                                    int start1, int end1,
125                                    int start2, int end2,
126                                    @Nullable Color fillColor,
127                                    @Nullable Color borderColor) {
128     if (fillColor != null) {
129       final int[] xPoints = new int[]{x1, x2, x2, x1};
130       final int[] yPoints = new int[]{start1, start2, end2 + 1, end1 + 1};
131
132       g.setColor(fillColor);
133       g.fillPolygon(xPoints, yPoints, xPoints.length);
134     }
135
136     if (borderColor != null) {
137       g.setColor(borderColor);
138       g.drawLine(x1, start1, x2, start2);
139       g.drawLine(x1, end1, x2, end2);
140     }
141   }
142
143   public static void drawCurveTrapezium(@NotNull Graphics2D g,
144                                         int x1, int x2,
145                                         int start1, int end1,
146                                         int start2, int end2,
147                                         @NotNull Color color) {
148     drawCurveTrapezium(g, x1, x2, start1, end1, start2, end2, color, null);
149   }
150
151   public static void drawCurveTrapezium(@NotNull Graphics2D g,
152                                         int x1, int x2,
153                                         int start1, int end1,
154                                         int start2, int end2,
155                                         @Nullable Color fillColor,
156                                         @Nullable Color borderColor) {
157     Shape upperCurve = makeCurve(x1, x2, start1, start2, true);
158     Shape lowerCurve = makeCurve(x1, x2, end1 + 1, end2 + 1, false);
159     Shape lowerCurveBorder = makeCurve(x1, x2, end1, end2, false);
160
161     if (fillColor != null) {
162       Path2D path = new Path2D.Double();
163       path.append(upperCurve, true);
164       path.append(lowerCurve, true);
165
166       g.setColor(fillColor);
167       g.fill(path);
168     }
169
170     if (borderColor != null) {
171       g.setColor(borderColor);
172       g.draw(upperCurve);
173       g.draw(lowerCurveBorder);
174     }
175   }
176
177   public static final double CTRL_PROXIMITY_X = 0.3;
178
179   private static Shape makeCurve(int x1, int x2, int y1, int y2, boolean forward) {
180     int width = x2 - x1;
181     if (forward) {
182       return new CubicCurve2D.Double(x1, y1,
183                                      x1 + width * CTRL_PROXIMITY_X, y1,
184                                      x1 + width * (1.0 - CTRL_PROXIMITY_X), y2,
185                                      x1 + width, y2);
186     }
187     else {
188       return new CubicCurve2D.Double(x1 + width, y2,
189                                      x1 + width * (1.0 - CTRL_PROXIMITY_X), y2,
190                                      x1 + width * CTRL_PROXIMITY_X, y1,
191                                      x1, y1);
192     }
193   }
194
195   //
196   // Impl
197   //
198
199   @NotNull
200   private static TextAttributes getTextAttributes(@NotNull final TextDiffType type,
201                                                   @Nullable final Editor editor,
202                                                   final boolean ignored) {
203     return new TextAttributes() {
204       @Override
205       public Color getBackgroundColor() {
206         return ignored ? type.getIgnoredColor(editor) : type.getColor(editor);
207       }
208     };
209   }
210
211   @NotNull
212   private static TextAttributes getStripeTextAttributes(@NotNull final TextDiffType type,
213                                                         @NotNull final Editor editor) {
214     return new TextAttributes() {
215       @Override
216       public Color getErrorStripeColor() {
217         return type.getMarkerColor(editor);
218       }
219     };
220   }
221
222   private static void installGutterRenderer(@NotNull RangeHighlighter highlighter,
223                                             @NotNull TextDiffType type,
224                                             boolean ignoredFoldingOutline,
225                                             boolean resolved) {
226     highlighter.setLineMarkerRenderer(new DiffLineMarkerRenderer(type, ignoredFoldingOutline, resolved));
227   }
228
229   private static void installEmptyRangeRenderer(@NotNull RangeHighlighter highlighter,
230                                                 @NotNull TextDiffType type) {
231     highlighter.setCustomRenderer(new DiffEmptyHighlighterRenderer(type));
232   }
233
234   //
235   // Highlighters
236   //
237
238   // TODO: invalid highlighting of empty last line
239   // TODO: invalid highlighting in case on deletion '\n' before range
240   // TODO: desync of range and line markers
241
242   @NotNull
243   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type) {
244     return createHighlighter(editor, start, end, type, false);
245   }
246
247   @NotNull
248   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
249                                                          boolean ignored) {
250     return createHighlighter(editor, start, end, type, ignored, HighlighterTargetArea.EXACT_RANGE);
251   }
252
253   @NotNull
254   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
255                                                          boolean ignored, @NotNull HighlighterTargetArea area) {
256     return createHighlighter(editor, start, end, type, ignored, area, false);
257   }
258
259   @NotNull
260   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
261                                                          boolean ignored, @NotNull HighlighterTargetArea area, boolean resolved) {
262     TextAttributes attributes = start != end && !resolved ? getTextAttributes(type, editor, ignored) : null;
263     TextAttributes stripeAttributes = start != end && !resolved ? getStripeTextAttributes(type, editor) : null;
264
265     RangeHighlighter highlighter = editor.getMarkupModel()
266       .addRangeHighlighter(start, end, DEFAULT_LAYER, attributes, area);
267
268     installGutterRenderer(highlighter, type, ignored, resolved);
269
270     if (stripeAttributes == null) return Collections.singletonList(highlighter);
271
272     RangeHighlighter stripeHighlighter = editor.getMarkupModel()
273       .addRangeHighlighter(start, end, STRIPE_LAYER, stripeAttributes, area);
274
275     return ContainerUtil.list(highlighter, stripeHighlighter);
276   }
277
278   @NotNull
279   public static List<RangeHighlighter> createInlineHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type) {
280     TextAttributes attributes = getTextAttributes(type, editor, false);
281
282     RangeHighlighter highlighter = editor.getMarkupModel()
283       .addRangeHighlighter(start, end, INLINE_LAYER, attributes, HighlighterTargetArea.EXACT_RANGE);
284
285     if (start == end) installEmptyRangeRenderer(highlighter, type);
286
287     return Collections.singletonList(highlighter);
288   }
289
290   @NotNull
291   public static List<RangeHighlighter> createLineMarker(@NotNull Editor editor, int line, @NotNull final TextDiffType type,
292                                                         @NotNull final SeparatorPlacement placement) {
293     return createLineMarker(editor, line, type, placement, false);
294   }
295
296   @NotNull
297   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final TextDiffType type,
298                                                         @NotNull final SeparatorPlacement placement, final boolean doubleLine) {
299     return createLineMarker(editor, line, type, placement, doubleLine, false);
300   }
301
302   @NotNull
303   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final TextDiffType type,
304                                                         @NotNull final SeparatorPlacement placement, final boolean doubleLine,
305                                                         final boolean applied) {
306     LineSeparatorRenderer renderer = new LineSeparatorRenderer() {
307       @Override
308       public void drawLine(Graphics g, int x1, int x2, int y) {
309         // TODO: change LineSeparatorRenderer interface ?
310         Rectangle clip = g.getClipBounds();
311         x2 = clip.x + clip.width;
312         drawChunkBorderLine((Graphics2D)g, x1, x2, y, type.getColor(editor), doubleLine, applied);
313       }
314     };
315     return createLineMarker(editor, line, placement, type, renderer, applied);
316   }
317
318   @NotNull
319   public static List<RangeHighlighter> createBorderLineMarker(@NotNull final Editor editor, int line,
320                                                               @NotNull final SeparatorPlacement placement) {
321     LineSeparatorRenderer renderer = new LineSeparatorRenderer() {
322       @Override
323       public void drawLine(Graphics g, int x1, int x2, int y) {
324         // TODO: change LineSeparatorRenderer interface ?
325         Rectangle clip = g.getClipBounds();
326         x2 = clip.x + clip.width;
327         g.setColor(JBColor.border());
328         g.drawLine(x1, y, x2, y);
329       }
330     };
331     return createLineMarker(editor, line, placement, null, renderer, false);
332   }
333
334   @NotNull
335   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final SeparatorPlacement placement,
336                                                         @Nullable TextDiffType type, @NotNull LineSeparatorRenderer renderer, boolean applied) {
337     // We won't use addLineHighlighter as it will fail to add marker into empty document.
338     //RangeHighlighter highlighter = editor.getMarkupModel().addLineHighlighter(line, HighlighterLayer.SELECTION - 1, null);
339
340     int offset = DocumentUtil.getFirstNonSpaceCharOffset(editor.getDocument(), line);
341     RangeHighlighter highlighter = editor.getMarkupModel()
342       .addRangeHighlighter(offset, offset, LINE_MARKER_LAYER, null, HighlighterTargetArea.LINES_IN_RANGE);
343
344     highlighter.setLineSeparatorPlacement(placement);
345     highlighter.setLineSeparatorRenderer(renderer);
346
347     if (type == null || applied) return Collections.singletonList(highlighter);
348
349     TextAttributes stripeAttributes = getStripeTextAttributes(type, editor);
350     RangeHighlighter stripeHighlighter = editor.getMarkupModel()
351       .addRangeHighlighter(offset, offset, STRIPE_LAYER, stripeAttributes, HighlighterTargetArea.LINES_IN_RANGE);
352
353     return ContainerUtil.list(highlighter, stripeHighlighter);
354   }
355
356   @NotNull
357   public static List<RangeHighlighter> createLineSeparatorHighlighter(@NotNull Editor editor,
358                                                                       int offset1,
359                                                                       int offset2,
360                                                                       @NotNull BooleanGetter condition) {
361     RangeHighlighter marker = editor.getMarkupModel()
362       .addRangeHighlighter(offset1, offset2, LINE_MARKER_LAYER, null, HighlighterTargetArea.LINES_IN_RANGE);
363
364     DiffLineSeparatorRenderer renderer = new DiffLineSeparatorRenderer(editor, condition);
365     marker.setLineSeparatorPlacement(SeparatorPlacement.TOP);
366     marker.setLineSeparatorRenderer(renderer);
367     marker.setLineMarkerRenderer(renderer);
368
369     return Collections.singletonList(marker);
370   }
371 }