1 package com.atlassian.theplugin.idea.crucible.editor;
2
3 import com.atlassian.theplugin.idea.IdeaVersionFacade;
4 import com.intellij.codeInsight.hint.EditorFragmentComponent;
5 import com.intellij.ide.highlighter.HighlighterFactory;
6 import com.intellij.openapi.actionSystem.*;
7 import com.intellij.openapi.diff.*;
8 import com.intellij.openapi.editor.Document;
9 import com.intellij.openapi.editor.Editor;
10 import com.intellij.openapi.editor.ScrollType;
11 import com.intellij.openapi.editor.colors.EditorColors;
12 import com.intellij.openapi.editor.colors.TextAttributesKey;
13 import com.intellij.openapi.editor.ex.DocumentEx;
14 import com.intellij.openapi.editor.ex.EditorEx;
15 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
16 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
17 import com.intellij.openapi.editor.impl.EditorImpl;
18 import com.intellij.openapi.editor.markup.ActiveGutterRenderer;
19 import com.intellij.openapi.fileEditor.FileDocumentManager;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.IconLoader;
22 import com.intellij.openapi.util.TextRange;
23 import com.intellij.openapi.vcs.VcsBundle;
24 import com.intellij.openapi.vcs.actions.AbstractVcsAction;
25 import com.intellij.openapi.vcs.actions.VcsContext;
26 import com.intellij.openapi.vcs.ex.Range;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.ui.HintListener;
29 import com.intellij.ui.LightweightHint;
30 import com.intellij.ui.SideBorder2;
31 import com.intellij.util.ui.UIUtil;
32
33 import javax.swing.*;
34 import java.awt.*;
35 import java.awt.event.MouseEvent;
36 import java.util.EventObject;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.ListIterator;
40
41
42 public class CrucibleDiffGutterRenderer implements ActiveGutterRenderer {
43 private static final int TWO_PIXEL = 2;
44 private static final int THREE_PIXEL = 3;
45 private static final int FOUR_PIXEL = 4;
46 private static final int INSERTED_RANGE = 2;
47 private static final int DELETED_RANGE = 3;
48 private static final int MODIFIED_RANGE = 1;
49
50 private static final int REFERENCE_TEXT_RANGE = 2;
51 private static final int DISPLAY_TEXT_RANGE = 3;
52
53 private final Range range;
54 private final Document referenceDocument;
55 private final Document displayDocument;
56 private final String fromRevision;
57 private final String toRevision;
58 private final Project project;
59 private List<Range> ranges;
60
61 public CrucibleDiffGutterRenderer(Project project, final List<Range> ranges, final Range range,
62 final Document referenceDocument, final Document displayDocument, final String fromRevision,
63 final String toRevision) {
64 this.project = project;
65 this.ranges = ranges;
66 this.range = range;
67 this.referenceDocument = referenceDocument;
68 this.displayDocument = displayDocument;
69 this.fromRevision = fromRevision;
70 this.toRevision = toRevision;
71 }
72
73 private TextAttributesKey getDiffColor(Range aRange) {
74 switch (aRange.getType()) {
75 case INSERTED_RANGE:
76 return DiffColors.DIFF_INSERTED;
77
78 case DELETED_RANGE:
79 return DiffColors.DIFF_DELETED;
80
81 case MODIFIED_RANGE:
82 return DiffColors.DIFF_MODIFIED;
83 default:
84 return null;
85 }
86 }
87
88 public void paint(Editor anEditor, Graphics g, Rectangle r) {
89 paintGutterFragment(anEditor, g, r, getDiffColor(range));
90
91 }
92
93 private void paintGutterFragment(Editor anEditor, Graphics g, Rectangle r, TextAttributesKey diffAttributeKey) {
94
95 EditorGutterComponentEx gutter = ((EditorEx) anEditor).getGutterComponentEx();
96 g.setColor(anEditor.getColorsScheme().getAttributes(diffAttributeKey).getBackgroundColor());
97 int endX = gutter.getWhitespaceSeparatorOffset();
98 int x = r.x + r.width - TWO_PIXEL;
99 int width = endX - x;
100 if (r.height > 0) {
101 g.fillRect(x, r.y + TWO_PIXEL, width, r.height - FOUR_PIXEL);
102 g.setColor(gutter.getFoldingColor(false));
103 UIUtil.drawLine(g, x, r.y + TWO_PIXEL, x + width, r.y + TWO_PIXEL);
104 UIUtil.drawLine(g, x, r.y + TWO_PIXEL, x, r.y + r.height - THREE_PIXEL);
105 UIUtil.drawLine(g, x, r.y + r.height - THREE_PIXEL, x + width, r.y + r.height - THREE_PIXEL);
106 } else {
107 int[] xPoints = new int[]{x,
108 x,
109 x + width - 1};
110 int[] yPoints = new int[]{r.y - FOUR_PIXEL,
111 r.y + FOUR_PIXEL,
112 r.y};
113 g.fillPolygon(xPoints, yPoints, THREE_PIXEL);
114
115 g.setColor(gutter.getFoldingColor(false));
116 g.drawPolygon(xPoints, yPoints, THREE_PIXEL);
117 }
118 }
119
120 public void doAction(Editor anEditor, MouseEvent e) {
121 e.consume();
122 JComponent comp = (JComponent) e.getComponent();
123 JLayeredPane layeredPane = comp.getRootPane().getLayeredPane();
124 Point point = SwingUtilities
125 .convertPoint(comp, ((EditorEx) anEditor).getGutterComponentEx().getWidth(), e.getY(), layeredPane);
126 showActiveHint(range, anEditor, point);
127 }
128
129 public boolean canDoAction(MouseEvent event) {
130 return true;
131 }
132
133 public Range getLastRange() {
134 if (ranges.size() > 0) {
135 return ranges.get(ranges.size() - 1);
136 }
137 return null;
138 }
139
140 public Range getNextRange(Range aRange) {
141 int j = ranges.indexOf(aRange);
142 if (j == ranges.size() - 1) {
143 return null;
144 } else {
145 return ranges.get(j + 1);
146 }
147 }
148
149 public Range getPrevRange(Range aRange) {
150 int j = ranges.indexOf(aRange);
151 if (j <= 0) {
152 return null;
153 } else {
154 return ranges.get(j - 1);
155 }
156 }
157
158 public Range getNextRange(int j) {
159 for (Iterator<Range> iterator = ranges.iterator(); iterator.hasNext();) {
160 Range myRange = iterator.next();
161 if (myRange.getOffset2() >= j) {
162 return myRange;
163 }
164 }
165
166 return null;
167 }
168
169 public Range getPrevRange(int j) {
170 for (ListIterator<Range> listiterator = ranges.listIterator(ranges.size()); listiterator.hasPrevious();) {
171 Range myRange = listiterator.previous();
172 if (myRange.getOffset1() <= j) {
173 return myRange;
174 }
175 }
176
177 return null;
178 }
179
180 public VirtualFile getReferenceVirtualFile() {
181 return FileDocumentManager.getInstance().getFile(referenceDocument);
182 }
183
184 public VirtualFile getDisplayVirtualFile() {
185 return FileDocumentManager.getInstance().getFile(displayDocument);
186 }
187
188
189 private TextRange getDisplayTextRange(Range aRange) {
190 return getTextRange(aRange.getType(), aRange.getOffset1(), aRange.getOffset2(), (byte) DISPLAY_TEXT_RANGE,
191 displayDocument);
192 }
193
194 private TextRange getReferenceTextRange(Range aRange) {
195 return getTextRange(aRange.getType(), aRange.getUOffset1(), aRange.getUOffset2(), (byte) REFERENCE_TEXT_RANGE,
196 referenceDocument);
197 }
198
199 private String getReferenceVirtualFileName() {
200 VirtualFile virtualfile = getReferenceVirtualFile();
201 if (virtualfile == null) {
202 return "";
203 } else {
204 return virtualfile.getName();
205 }
206 }
207
208 private static TextRange getTextRange(byte byte0, int j, int k, byte byte1, Document document) {
209 if (byte0 == byte1) {
210 int l;
211 if (j == 0) {
212 l = 0;
213 } else {
214 l = document.getLineEndOffset(j - 1);
215 }
216 return new TextRange(l, l);
217 }
218 int i1 = document.getLineStartOffset(j);
219 int j1 = document.getLineEndOffset(k - 1);
220 if (i1 > 0) {
221 i1--;
222 j1--;
223 }
224 return new TextRange(i1, j1);
225 }
226
227
228 public void moveToRange(final Range aRange, final Editor anEditor,
229 final Document aDisplayDocument, final boolean showHint) {
230 final int firstOffset = aDisplayDocument
231 .getLineStartOffset(Math.min(aRange.getOffset1(), aDisplayDocument.getLineCount() - 1));
232 anEditor.getCaretModel().moveToOffset(firstOffset);
233 anEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
234 anEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
235
236 public void run() {
237 java.awt.Point point = anEditor.visualPositionToXY(anEditor.offsetToVisualPosition(firstOffset));
238 JComponent jcomponent = anEditor.getContentComponent();
239 javax.swing.JLayeredPane jlayeredpane = jcomponent.getRootPane().getLayeredPane();
240 point = SwingUtilities.convertPoint(jcomponent, 0, point.y, jlayeredpane);
241 if (showHint) {
242 showActiveHint(aRange, anEditor, point);
243 }
244 }
245 });
246 }
247
248 private abstract class ShowChangeMarkerAction extends AbstractVcsAction {
249 protected Range range;
250 protected Editor editor;
251 protected Document document;
252
253 protected abstract Range extractRange(ChangeViewer aHighlighter, int i, Editor anEditor);
254
255 public ShowChangeMarkerAction(final Range range, final Editor anEditor, final Document document) {
256 this.range = range;
257
258 this.editor = anEditor;
259 this.document = document;
260 }
261
262 @Override
263 protected boolean forceSyncUpdate(AnActionEvent anactionevent) {
264 return true;
265 }
266
267 private boolean checkPosition(VcsContext vcscontext) {
268 return range != null;
269 }
270
271 @Override
272 protected void update(VcsContext vcscontext, Presentation presentation) {
273 presentation.setEnabled(checkPosition(vcscontext));
274 }
275
276 @Override
277 protected void actionPerformed(VcsContext vcscontext) {
278 moveToRange(range, editor, document, true);
279 }
280 }
281
282 private class ShowPrevChangeMarkerAction extends ShowChangeMarkerAction {
283
284 public ShowPrevChangeMarkerAction(Range range, Editor anEditor, Document document) {
285 super(range, anEditor, document);
286 }
287
288 @Override
289 protected Range extractRange(ChangeViewer aHighlighter, int i, Editor anEditor) {
290 return getPrevRange(i);
291 }
292 }
293
294 private class ShowNextChangeMarkerAction extends ShowChangeMarkerAction {
295
296 public ShowNextChangeMarkerAction(Range range, Editor editor, Document document) {
297 super(range, editor, document);
298 }
299
300 @Override
301 protected Range extractRange(ChangeViewer aHighlighter, int i, Editor anEditor) {
302 return getNextRange(i);
303 }
304 }
305
306
307 private class ShowDiffAction extends AnAction {
308 protected final Range myRange;
309 private final Project project;
310 private final Document referenceDocument;
311 private final Document displayDocument;
312
313 @Override
314 public void update(final AnActionEvent e) {
315 e.getPresentation().setEnabled(checkModified() || checkDeleted());
316 }
317
318 private boolean checkDeleted() {
319 return myRange.getType() == DELETED_RANGE;
320 }
321
322 private boolean checkModified() {
323 return myRange.getType() == MODIFIED_RANGE;
324 }
325
326 @Override
327 public void actionPerformed(AnActionEvent anactionevent) {
328 DiffManager.getInstance().getDiffTool().show(prepareDiffRequest());
329 }
330
331 private DiffRequest prepareDiffRequest() {
332 return new DiffRequest(project) {
333
334 @Override
335 public DiffContent[] getContents() {
336 return (new DiffContent[]{
337 getDiffContent(referenceDocument, getReferenceTextRange(myRange),
338 getReferenceVirtualFile()),
339 getDiffContent(displayDocument, getDisplayTextRange(myRange),
340 getDisplayVirtualFile())
341 });
342 }
343
344 @Override
345 public String[] getContentTitles() {
346 return (new String[]{
347 VcsBundle.message("diff.content.title.repository.version", new Object[]{fromRevision}),
348 VcsBundle.message("diff.content.title.repository.version", new Object[]{toRevision})
349 });
350 }
351
352 @Override
353 public String getWindowTitle() {
354 return VcsBundle.message("dialog.title.diff.for.range", new Object[0]);
355 }
356 };
357 }
358
359 private DiffContent getDiffContent(Document document, TextRange textrange, VirtualFile virtualfile) {
360 DocumentContent documentcontent = new DocumentContent(project, document);
361 return new FragmentContent(documentcontent, textrange, project, virtualfile);
362 }
363
364 public ShowDiffAction(Project project, Document referenceDocument, Document displayDocument, Range range) {
365 super(VcsBundle.message("action.name.show.difference"), null, IconLoader.getIcon("/actions/diff.png"));
366 this.project = project;
367 this.referenceDocument = referenceDocument;
368 this.displayDocument = displayDocument;
369 myRange = range;
370 }
371 }
372
373
374 public void showActiveHint(Range aRange, final Editor anEditor, Point point) {
375
376 DefaultActionGroup group = new DefaultActionGroup();
377
378 final AnAction globalShowNextAction = ActionManager.getInstance().getAction("VcsShowNextChangeMarker");
379 final AnAction globalShowPrevAction = ActionManager.getInstance().getAction("VcsShowPrevChangeMarker");
380
381 final ShowPrevChangeMarkerAction localShowPrevAction = new ShowPrevChangeMarkerAction(getPrevRange(aRange),
382 anEditor, displayDocument);
383 final ShowNextChangeMarkerAction localShowNextAction = new ShowNextChangeMarkerAction(getNextRange(aRange),
384 anEditor, displayDocument);
385
386 JComponent editorComponent = anEditor.getComponent();
387
388 localShowNextAction.registerCustomShortcutSet(localShowNextAction.getShortcutSet(), editorComponent);
389 localShowPrevAction.registerCustomShortcutSet(localShowPrevAction.getShortcutSet(), editorComponent);
390
391 group.add(localShowPrevAction);
392 group.add(localShowNextAction);
393
394 localShowNextAction.copyFrom(globalShowNextAction);
395 localShowPrevAction.copyFrom(globalShowPrevAction);
396
397 group.add(new ShowDiffAction(project, referenceDocument, displayDocument, aRange));
398
399 @SuppressWarnings("unchecked")
400 final java.util.List<AnAction> actionList = (java.util.List<AnAction>) editorComponent
401 .getClientProperty(AnAction.ourClientProperty);
402
403 actionList.remove(globalShowPrevAction);
404 actionList.remove(globalShowNextAction);
405
406 JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true)
407 .getComponent();
408
409 final Color background = ((EditorEx) anEditor).getBackroundColor();
410 final Color foreground = anEditor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
411 toolbar.setBackground(background);
412
413 toolbar.setBorder(
414 new SideBorder2(foreground, foreground, aRange.getType() != Range.INSERTED ? null : foreground, foreground, 1));
415
416 JPanel component = new JPanel(new BorderLayout());
417 component.setOpaque(false);
418
419 JPanel toolbarPanel = new JPanel(new BorderLayout());
420 toolbarPanel.setOpaque(false);
421 toolbarPanel.add(toolbar, BorderLayout.WEST);
422 component.add(toolbarPanel, BorderLayout.NORTH);
423
424 if (aRange.getType() != Range.INSERTED) {
425 DocumentEx doc = (DocumentEx) referenceDocument;
426 EditorImpl uEditor = new EditorImpl(doc, true, project);
427 EditorHighlighter highlighter = HighlighterFactory
428 .createHighlighter(project, getReferenceVirtualFileName());
429 uEditor.setHighlighter(highlighter);
430
431 EditorFragmentComponent editorFragmentComponent =
432 EditorFragmentComponent
433 .createEditorFragmentComponent(uEditor, aRange.getUOffset1(), aRange.getUOffset2(), false, false);
434
435 component.add(editorFragmentComponent, BorderLayout.CENTER);
436 }
437
438 LightweightHint lightweightHint = new LightweightHint(component);
439 lightweightHint.addHintListener(new HintListener() {
440 public void hintHidden(EventObject event) {
441 actionList.remove(localShowPrevAction);
442 actionList.remove(localShowNextAction);
443 actionList.add(globalShowPrevAction);
444 actionList.add(globalShowNextAction);
445 }
446 });
447
448 IdeaVersionFacade.getInstance().showEditorHints(lightweightHint, anEditor, point);
449
450 }
451 }