View Javadoc

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(); // shall be EditorGutterComponent, cast is safe.
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 }