Remove System.out.println from RevWalkFilterTest
[jgit.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / decorators / GitLightweightDecorator.java
blob6f69c805f3a7bd40ca3edc8bb31b6d0483763db0
1 /*******************************************************************************
2 * Copyright (C) 2007, IBM Corporation and others
3 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
4 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
5 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Copyright (C) 2008, Google Inc.
7 * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * See LICENSE for the full license text, also available.
12 *******************************************************************************/
14 package org.spearce.egit.ui.internal.decorators;
16 import java.io.IOException;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.Map;
20 import java.util.Set;
22 import org.eclipse.core.resources.IProject;
23 import org.eclipse.core.resources.IResource;
24 import org.eclipse.core.resources.IResourceChangeEvent;
25 import org.eclipse.core.resources.IResourceChangeListener;
26 import org.eclipse.core.resources.IResourceDelta;
27 import org.eclipse.core.resources.IResourceDeltaVisitor;
28 import org.eclipse.core.resources.ResourcesPlugin;
29 import org.eclipse.core.resources.mapping.ResourceMapping;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IAdaptable;
32 import org.eclipse.core.runtime.IStatus;
33 import org.eclipse.jface.preference.IPreferenceStore;
34 import org.eclipse.jface.resource.ImageDescriptor;
35 import org.eclipse.jface.util.IPropertyChangeListener;
36 import org.eclipse.jface.util.PropertyChangeEvent;
37 import org.eclipse.jface.viewers.IDecoration;
38 import org.eclipse.jface.viewers.ILightweightLabelDecorator;
39 import org.eclipse.jface.viewers.LabelProvider;
40 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
41 import org.eclipse.osgi.util.TextProcessor;
42 import org.eclipse.swt.graphics.ImageData;
43 import org.eclipse.swt.widgets.Display;
44 import org.eclipse.team.ui.ISharedImages;
45 import org.eclipse.team.ui.TeamImages;
46 import org.eclipse.team.ui.TeamUI;
47 import org.eclipse.ui.IContributorResourceAdapter;
48 import org.eclipse.ui.PlatformUI;
49 import org.spearce.egit.core.GitException;
50 import org.spearce.egit.core.internal.util.ExceptionCollector;
51 import org.spearce.egit.core.project.GitProjectData;
52 import org.spearce.egit.core.project.RepositoryChangeListener;
53 import org.spearce.egit.core.project.RepositoryMapping;
54 import org.spearce.egit.ui.Activator;
55 import org.spearce.egit.ui.UIIcons;
56 import org.spearce.egit.ui.UIPreferences;
57 import org.spearce.egit.ui.UIText;
58 import org.spearce.egit.ui.internal.decorators.IDecoratableResource.Staged;
59 import org.spearce.jgit.lib.IndexChangedEvent;
60 import org.spearce.jgit.lib.RefsChangedEvent;
61 import org.spearce.jgit.lib.Repository;
62 import org.spearce.jgit.lib.RepositoryChangedEvent;
63 import org.spearce.jgit.lib.RepositoryListener;
65 /**
66 * Supplies annotations for displayed resources
68 * This decorator provides annotations to indicate the status of each resource
69 * when compared to <code>HEAD</code>, as well as the index in the relevant
70 * repository.
72 * TODO: Add support for colors and font decoration
74 public class GitLightweightDecorator extends LabelProvider implements
75 ILightweightLabelDecorator, IPropertyChangeListener,
76 IResourceChangeListener, RepositoryChangeListener, RepositoryListener {
78 /**
79 * Property constant pointing back to the extension point id of the
80 * decorator
82 public static final String DECORATOR_ID = "org.spearce.egit.ui.internal.decorators.GitLightweightDecorator"; //$NON-NLS-1$
84 /**
85 * Bit-mask describing interesting changes for IResourceChangeListener
86 * events
88 private static int INTERESTING_CHANGES = IResourceDelta.CONTENT
89 | IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO
90 | IResourceDelta.OPEN | IResourceDelta.REPLACED
91 | IResourceDelta.TYPE;
93 /**
94 * Collector for keeping the error view from filling up with exceptions
96 private static ExceptionCollector exceptions = new ExceptionCollector(
97 UIText.Decorator_exceptionMessage, Activator.getPluginId(),
98 IStatus.ERROR, Activator.getDefault().getLog());
101 * Constructs a new Git resource decorator
103 public GitLightweightDecorator() {
104 TeamUI.addPropertyChangeListener(this);
105 Activator.addPropertyChangeListener(this);
106 PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
107 .addPropertyChangeListener(this);
108 Repository.addAnyRepositoryChangedListener(this);
109 GitProjectData.addRepositoryChangeListener(this);
110 ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
111 IResourceChangeEvent.POST_CHANGE);
115 * (non-Javadoc)
117 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
119 @Override
120 public void dispose() {
121 super.dispose();
122 PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
123 .removePropertyChangeListener(this);
124 TeamUI.removePropertyChangeListener(this);
125 Activator.removePropertyChangeListener(this);
126 Repository.removeAnyRepositoryChangedListener(this);
127 GitProjectData.removeRepositoryChangeListener(this);
128 ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
132 * This method should only be called by the decorator thread.
134 * @see org.eclipse.jface.viewers.ILightweightLabelDecorator#decorate(java.lang.Object,
135 * org.eclipse.jface.viewers.IDecoration)
137 public void decorate(Object element, IDecoration decoration) {
138 final IResource resource = getResource(element);
139 if (resource == null)
140 return;
142 // Don't decorate if the workbench is not running
143 if (!PlatformUI.isWorkbenchRunning())
144 return;
146 // Don't decorate if UI plugin is not running
147 Activator activator = Activator.getDefault();
148 if (activator == null)
149 return;
151 // Don't decorate the workspace root
152 if (resource.getType() == IResource.ROOT)
153 return;
155 // Don't decorate non-existing resources
156 if (!resource.exists() && !resource.isPhantom())
157 return;
159 // Make sure we're dealing with a project under Git revision control
160 final RepositoryMapping mapping = RepositoryMapping
161 .getMapping(resource);
162 if (mapping == null)
163 return;
165 // Cannot decorate linked resources
166 if (mapping.getRepoRelativePath(resource) == null)
167 return;
169 try {
170 DecorationHelper helper = new DecorationHelper(activator
171 .getPreferenceStore());
172 helper.decorate(decoration,
173 new DecoratableResourceAdapter(resource));
174 } catch (IOException e) {
175 handleException(resource, GitException.wrapException(e));
180 * Helper class for doing resource decoration, based on the given
181 * preferences
183 * Used for real-time decoration, as well as in the decorator preview
184 * preferences page
186 public static class DecorationHelper {
188 /** */
189 public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$
191 /** */
192 public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$
194 /** */
195 public static final String BINDING_DIRTY_FLAG = "dirty"; //$NON-NLS-1$
197 /** */
198 public static final String BINDING_STAGED_FLAG = "staged"; //$NON-NLS-1$
200 private IPreferenceStore store;
203 * Define a cached image descriptor which only creates the image data
204 * once
206 private static class CachedImageDescriptor extends ImageDescriptor {
207 ImageDescriptor descriptor;
209 ImageData data;
211 public CachedImageDescriptor(ImageDescriptor descriptor) {
212 this.descriptor = descriptor;
215 public ImageData getImageData() {
216 if (data == null) {
217 data = descriptor.getImageData();
219 return data;
223 private static ImageDescriptor trackedImage;
225 private static ImageDescriptor untrackedImage;
227 private static ImageDescriptor stagedImage;
229 private static ImageDescriptor stagedAddedImage;
231 private static ImageDescriptor stagedRemovedImage;
233 private static ImageDescriptor conflictImage;
235 private static ImageDescriptor assumeValidImage;
237 static {
238 trackedImage = new CachedImageDescriptor(TeamImages
239 .getImageDescriptor(ISharedImages.IMG_CHECKEDIN_OVR));
240 untrackedImage = new CachedImageDescriptor(UIIcons.OVR_UNTRACKED);
241 stagedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED);
242 stagedAddedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED_ADD);
243 stagedRemovedImage = new CachedImageDescriptor(
244 UIIcons.OVR_STAGED_REMOVE);
245 conflictImage = new CachedImageDescriptor(UIIcons.OVR_CONFLICT);
246 assumeValidImage = new CachedImageDescriptor(UIIcons.OVR_ASSUMEVALID);
250 * Constructs a decorator using the rules from the given
251 * <code>preferencesStore</code>
253 * @param preferencesStore
254 * the preferences store with the preferred decorator rules
256 public DecorationHelper(IPreferenceStore preferencesStore) {
257 store = preferencesStore;
261 * Decorates the given <code>decoration</code> based on the state of the
262 * given <code>resource</code>, using the preferences passed when
263 * constructing this decoration helper.
265 * @param decoration
266 * the decoration to decorate
267 * @param resource
268 * the resource to retrieve state from
270 public void decorate(IDecoration decoration,
271 IDecoratableResource resource) {
272 if (resource.isIgnored())
273 return;
275 decorateText(decoration, resource);
276 decorateIcons(decoration, resource);
279 private void decorateText(IDecoration decoration,
280 IDecoratableResource resource) {
281 String format = "";
282 switch (resource.getType()) {
283 case IResource.FILE:
284 format = store
285 .getString(UIPreferences.DECORATOR_FILETEXT_DECORATION);
286 break;
287 case IResource.FOLDER:
288 format = store
289 .getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION);
290 break;
291 case IResource.PROJECT:
292 format = store
293 .getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION);
294 break;
297 Map<String, String> bindings = new HashMap<String, String>();
298 bindings.put(BINDING_RESOURCE_NAME, resource.getName());
299 bindings.put(BINDING_BRANCH_NAME, resource.getBranch());
300 bindings.put(BINDING_DIRTY_FLAG, resource.isDirty() ? ">" : null);
301 bindings.put(BINDING_STAGED_FLAG,
302 resource.staged() != Staged.NOT_STAGED ? "*" : null);
304 decorate(decoration, format, bindings);
307 private void decorateIcons(IDecoration decoration,
308 IDecoratableResource resource) {
309 ImageDescriptor overlay = null;
311 if (resource.isTracked()) {
312 if (store.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON))
313 overlay = trackedImage;
315 if (store
316 .getBoolean(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON)
317 && resource.isAssumeValid())
318 overlay = assumeValidImage;
320 // Staged overrides tracked
321 Staged staged = resource.staged();
322 if (store.getBoolean(UIPreferences.DECORATOR_SHOW_STAGED_ICON)
323 && staged != Staged.NOT_STAGED) {
324 if (staged == Staged.ADDED)
325 overlay = stagedAddedImage;
326 else if (staged == Staged.REMOVED)
327 overlay = stagedRemovedImage;
328 else
329 overlay = stagedImage;
332 // Conflicts override everything
333 if (store
334 .getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON)
335 && resource.hasConflicts())
336 overlay = conflictImage;
338 } else if (store
339 .getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
340 overlay = untrackedImage;
343 // Overlays can only be added once, so do it at the end
344 decoration.addOverlay(overlay);
348 * Decorates the given <code>decoration</code>, using the specified text
349 * <code>format</code>, and mapped using the variable bindings from
350 * <code>bindings</code>
352 * @param decoration
353 * the decoration to decorate
354 * @param format
355 * the format to base the decoration on
356 * @param bindings
357 * the bindings between variables in the format and actual
358 * values
360 public static void decorate(IDecoration decoration, String format,
361 Map<String, String> bindings) {
362 StringBuffer prefix = new StringBuffer();
363 StringBuffer suffix = new StringBuffer();
364 StringBuffer output = prefix;
366 int length = format.length();
367 int start = -1;
368 int end = length;
369 while (true) {
370 if ((end = format.indexOf('{', start)) > -1) {
371 output.append(format.substring(start + 1, end));
372 if ((start = format.indexOf('}', end)) > -1) {
373 String key = format.substring(end + 1, start);
374 String s;
376 // Allow users to override the binding
377 if (key.indexOf(':') > -1) {
378 String[] keyAndBinding = key.split(":", 2);
379 key = keyAndBinding[0];
380 if (keyAndBinding.length > 1
381 && bindings.get(key) != null)
382 bindings.put(key, keyAndBinding[1]);
385 // We use the BINDING_RESOURCE_NAME key to determine if
386 // we are doing the prefix or suffix. The name isn't
387 // actually part of either.
388 if (key.equals(BINDING_RESOURCE_NAME)) {
389 output = suffix;
390 s = null;
391 } else {
392 s = bindings.get(key);
395 if (s != null) {
396 output.append(s);
397 } else {
398 // Support removing prefix character if binding is
399 // null
400 int curLength = output.length();
401 if (curLength > 0) {
402 char c = output.charAt(curLength - 1);
403 if (c == ':' || c == '@') {
404 output.deleteCharAt(curLength - 1);
408 } else {
409 output.append(format.substring(end, length));
410 break;
412 } else {
413 output.append(format.substring(start + 1, length));
414 break;
418 String prefixString = prefix.toString().replaceAll("^\\s+", "");
419 if (prefixString != null) {
420 decoration.addPrefix(TextProcessor.process(prefixString,
421 "()[].")); //$NON-NLS-1$
423 String suffixString = suffix.toString().replaceAll("\\s+$", "");
424 if (suffixString != null) {
425 decoration.addSuffix(TextProcessor.process(suffixString,
426 "()[].")); //$NON-NLS-1$
431 // -------- Refresh handling --------
434 * Perform a blanket refresh of all decorations
436 public static void refresh() {
437 Display.getDefault().asyncExec(new Runnable() {
438 public void run() {
439 Activator.getDefault().getWorkbench().getDecoratorManager()
440 .update(DECORATOR_ID);
446 * Callback for IPropertyChangeListener events
448 * If any of the relevant preferences has been changed we refresh all
449 * decorations (all projects and their resources).
451 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
453 public void propertyChange(PropertyChangeEvent event) {
454 final String prop = event.getProperty();
455 // If the property is of any interest to us
456 if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED)
457 || prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED)
458 || prop.equals(Activator.DECORATORS_CHANGED)) {
459 postLabelEvent(new LabelProviderChangedEvent(this));
464 * Callback for IResourceChangeListener events
466 * Schedules a refresh of the changed resource
468 * If the preference for computing deep dirty states has been set we walk
469 * the ancestor tree of the changed resource and update all parents as well.
471 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
473 public void resourceChanged(IResourceChangeEvent event) {
474 final Set<IResource> resourcesToUpdate = new HashSet<IResource>();
476 try { // Compute the changed resources by looking at the delta
477 event.getDelta().accept(new IResourceDeltaVisitor() {
478 public boolean visit(IResourceDelta delta) throws CoreException {
480 // If the file has changed but not in a way that we care
481 // about (e.g. marker changes to files) then ignore
482 if (delta.getKind() == IResourceDelta.CHANGED
483 && (delta.getFlags() & INTERESTING_CHANGES) == 0) {
484 return true;
487 final IResource resource = delta.getResource();
489 // If the resource is not part of a project under Git
490 // revision control
491 final RepositoryMapping mapping = RepositoryMapping
492 .getMapping(resource);
493 if (mapping == null) {
494 // Ignore the change
495 return true;
498 if (resource.getType() == IResource.ROOT) {
499 // Continue with the delta
500 return true;
503 if (resource.getType() == IResource.PROJECT) {
504 // If the project is not accessible, don't process it
505 if (!resource.isAccessible())
506 return false;
509 // All seems good, schedule the resource for update
510 resourcesToUpdate.add(resource);
512 if (delta.getKind() == IResourceDelta.CHANGED
513 && (delta.getFlags() & IResourceDelta.OPEN) > 1)
514 return false; // Don't recurse when opening projects
515 else
516 return true;
518 }, true /* includePhantoms */);
519 } catch (final CoreException e) {
520 handleException(null, e);
523 if (resourcesToUpdate.isEmpty())
524 return;
526 // If ancestor-decoration is enabled in the preferences we walk
527 // the ancestor tree of each of the changed resources and add
528 // their parents to the update set
529 final IPreferenceStore store = Activator.getDefault()
530 .getPreferenceStore();
531 if (store.getBoolean(UIPreferences.DECORATOR_RECOMPUTE_ANCESTORS)) {
532 final IResource[] changedResources = resourcesToUpdate
533 .toArray(new IResource[resourcesToUpdate.size()]);
534 for (IResource current : changedResources) {
535 while (current.getType() != IResource.ROOT) {
536 current = current.getParent();
537 resourcesToUpdate.add(current);
542 postLabelEvent(new LabelProviderChangedEvent(this, resourcesToUpdate
543 .toArray()));
547 * Callback for RepositoryListener events
549 * We resolve the repository mapping for the changed repository and forward
550 * that to repositoryChanged(RepositoryMapping).
552 * @param e
553 * The original change event
555 private void repositoryChanged(RepositoryChangedEvent e) {
556 final Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
557 for (final IProject p : ResourcesPlugin.getWorkspace().getRoot()
558 .getProjects()) {
559 final RepositoryMapping mapping = RepositoryMapping.getMapping(p);
560 if (mapping != null && mapping.getRepository() == e.getRepository())
561 ms.add(mapping);
563 for (final RepositoryMapping m : ms) {
564 repositoryChanged(m);
569 * (non-Javadoc)
571 * @see
572 * org.spearce.jgit.lib.RepositoryListener#indexChanged(org.spearce.jgit
573 * .lib.IndexChangedEvent)
575 public void indexChanged(IndexChangedEvent e) {
576 repositoryChanged(e);
580 * (non-Javadoc)
582 * @see
583 * org.spearce.jgit.lib.RepositoryListener#refsChanged(org.spearce.jgit.
584 * lib.RefsChangedEvent)
586 public void refsChanged(RefsChangedEvent e) {
587 repositoryChanged(e);
591 * Callback for RepositoryChangeListener events, as well as
592 * RepositoryListener events via repositoryChanged()
594 * @see org.spearce.egit.core.project.RepositoryChangeListener#repositoryChanged(org.spearce.egit.core.project.RepositoryMapping)
596 public void repositoryChanged(RepositoryMapping mapping) {
597 // Until we find a way to refresh visible labels within a project
598 // we have to use this blanket refresh that includes all projects.
599 postLabelEvent(new LabelProviderChangedEvent(this));
602 // -------- Helper methods --------
604 private static IResource getResource(Object element) {
605 if (element instanceof ResourceMapping) {
606 element = ((ResourceMapping) element).getModelObject();
609 IResource resource = null;
610 if (element instanceof IResource) {
611 resource = (IResource) element;
612 } else if (element instanceof IAdaptable) {
613 final IAdaptable adaptable = (IAdaptable) element;
614 resource = (IResource) adaptable.getAdapter(IResource.class);
615 if (resource == null) {
616 final IContributorResourceAdapter adapter = (IContributorResourceAdapter) adaptable
617 .getAdapter(IContributorResourceAdapter.class);
618 if (adapter != null)
619 resource = adapter.getAdaptedResource(adaptable);
623 return resource;
627 * Post the label event to the UI thread
629 * @param event
630 * The event to post
632 private void postLabelEvent(final LabelProviderChangedEvent event) {
633 Display.getDefault().asyncExec(new Runnable() {
634 public void run() {
635 fireLabelProviderChanged(event);
641 * Handle exceptions that occur in the decorator. Exceptions are only logged
642 * for resources that are accessible (i.e. exist in an open project).
644 * @param resource
645 * The resource that triggered the exception
646 * @param e
647 * The exception that occurred
649 private static void handleException(IResource resource, CoreException e) {
650 if (resource == null || resource.isAccessible())
651 exceptions.handleException(e);