| /******************************************************************************* |
| * Copyright (c) 2000, 2015 QNX Software Systems and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * QNX Software Systems - Initial API and implementation |
| * Anton Leherbauer (Wind River Systems) |
| *******************************************************************************/ |
| package org.eclipse.cdt.ui; |
| |
| import java.util.HashSet; |
| |
| import org.eclipse.cdt.core.model.CModelException; |
| import org.eclipse.cdt.core.model.CoreModel; |
| import org.eclipse.cdt.core.model.ElementChangedEvent; |
| import org.eclipse.cdt.core.model.IArchive; |
| import org.eclipse.cdt.core.model.IArchiveContainer; |
| import org.eclipse.cdt.core.model.IBinary; |
| import org.eclipse.cdt.core.model.IBinaryContainer; |
| import org.eclipse.cdt.core.model.ICContainer; |
| import org.eclipse.cdt.core.model.ICElement; |
| import org.eclipse.cdt.core.model.ICElementDelta; |
| import org.eclipse.cdt.core.model.ICModel; |
| import org.eclipse.cdt.core.model.ICProject; |
| import org.eclipse.cdt.core.model.IElementChangedListener; |
| import org.eclipse.cdt.core.model.IParent; |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.cdt.core.model.IWorkingCopy; |
| import org.eclipse.cdt.internal.core.model.ArchiveContainer; |
| import org.eclipse.cdt.internal.core.model.BinaryContainer; |
| import org.eclipse.cdt.internal.ui.BaseCElementContentProvider; |
| import org.eclipse.cdt.internal.ui.actions.SelectionConverter; |
| import org.eclipse.cdt.internal.ui.text.CWordFinder; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.information.IInformationProvider; |
| import org.eclipse.jface.text.information.IInformationProviderExtension; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| /** |
| * A content provider for C elements. |
| * <p> |
| * The following C element hierarchy is surfaced by this content provider: |
| * <p> |
| * <pre> |
| C model (<code>ICModel</code>)<br> |
| C project (<code>ICProject</code>)<br> |
| Virtual binaries container(<code>IBinaryContainery</code>) |
| Virtual archives container(<code>IArchiveContainery</code>) |
| Source root (<code>ISourceRoot</code>)<br> |
| C Container(folders) (<code>ICContainer</code>)<br> |
| Translation unit (<code>ITranslationUnit</code>)<br> |
| Binary file (<code>IBinary</code>)<br> |
| Archive file (<code>IArchive</code>)<br> |
| Non C Resource file (<code>Object</code>)<br> |
| |
| * </pre> |
| */ |
| public class CElementContentProvider extends BaseCElementContentProvider |
| implements IElementChangedListener, IInformationProvider, IInformationProviderExtension { |
| /** Editor. */ |
| protected ITextEditor fEditor; |
| protected StructuredViewer fViewer; |
| protected Object fInput; |
| |
| /** Remember what refreshes we already have pending so we don't post them again. */ |
| protected HashSet<IRefreshable> pendingRefreshes = new HashSet<>(); |
| |
| /** |
| * Creates a new content provider for C elements. |
| */ |
| public CElementContentProvider() { |
| // Empty. |
| } |
| |
| /** |
| * Creates a new content provider for C elements. |
| * @param editor Editor. |
| */ |
| public CElementContentProvider(ITextEditor editor) { |
| fEditor = editor; |
| } |
| |
| /** |
| * Creates a new content provider for C elements. |
| */ |
| public CElementContentProvider(boolean provideMembers, boolean provideWorkingCopy) { |
| super(provideMembers, provideWorkingCopy); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| CoreModel.getDefault().removeElementChangedListener(this); |
| } |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| super.inputChanged(viewer, oldInput, newInput); |
| |
| fViewer = (StructuredViewer) viewer; |
| |
| if (oldInput == null && newInput != null) { |
| CoreModel.getDefault().addElementChangedListener(this); |
| } else if (oldInput != null && newInput == null) { |
| CoreModel.getDefault().removeElementChangedListener(this); |
| } |
| fInput = newInput; |
| } |
| |
| @Override |
| public void elementChanged(final ElementChangedEvent event) { |
| try { |
| processDelta(event.getDelta()); |
| } catch (CModelException e) { |
| CUIPlugin.log(e); |
| e.printStackTrace(); |
| } |
| } |
| |
| protected boolean isPathEntryChange(ICElementDelta delta) { |
| int flags = delta.getFlags(); |
| return (delta.getKind() == ICElementDelta.CHANGED && ((flags & ICElementDelta.F_BINARY_PARSER_CHANGED) != 0 |
| || (flags & ICElementDelta.F_ADDED_PATHENTRY_LIBRARY) != 0 |
| || (flags & ICElementDelta.F_ADDED_PATHENTRY_SOURCE) != 0 |
| || (flags & ICElementDelta.F_REMOVED_PATHENTRY_LIBRARY) != 0 |
| || (flags & ICElementDelta.F_PATHENTRY_REORDER) != 0 |
| || (flags & ICElementDelta.F_REMOVED_PATHENTRY_SOURCE) != 0 |
| || (flags & ICElementDelta.F_CHANGED_PATHENTRY_INCLUDE) != 0)); |
| } |
| |
| /** |
| * Processes a delta recursively. When more than two children are affected the |
| * tree is fully refreshed starting at this node. The delta is processed in the |
| * current thread but the viewer updates are posted to the UI thread. |
| */ |
| protected void processDelta(ICElementDelta delta) throws CModelException { |
| int kind = delta.getKind(); |
| int flags = delta.getFlags(); |
| ICElement element = delta.getElement(); |
| |
| //System.out.println("Processing " + element); |
| |
| // handle open and closing of a project |
| if (((flags & ICElementDelta.F_CLOSED) != 0) || ((flags & ICElementDelta.F_OPENED) != 0)) { |
| postRefresh(element); |
| return; |
| } |
| |
| // We do not care about changes in Working copies |
| // well, we do see bug 147694 |
| if (element instanceof ITranslationUnit) { |
| ITranslationUnit unit = (ITranslationUnit) element; |
| if (unit.isWorkingCopy()) { |
| if (!getProvideWorkingCopy() || kind == ICElementDelta.REMOVED || kind == ICElementDelta.ADDED) { |
| return; |
| } |
| } |
| if (!getProvideMembers() && kind == ICElementDelta.CHANGED) { |
| return; |
| } |
| } |
| |
| if (kind == ICElementDelta.REMOVED) { |
| postRemove(element); |
| updateContainer(element); |
| return; |
| } |
| |
| if (kind == ICElementDelta.ADDED) { |
| Object parent = internalGetParent(element); |
| postAdd(parent, element); |
| updateContainer(element); |
| } |
| |
| if (isPathEntryChange(delta)) { |
| postRefresh(element.getCProject()); |
| return; |
| } |
| |
| if (kind == ICElementDelta.CHANGED) { |
| // Binary/Archive changes is done differently since they |
| // are at two places, they are in the {Binary,Archive}Container |
| // and in the Tree hierarchy |
| if (updateContainer(element)) { |
| Object parent = getParent(element); |
| postRefresh(parent); |
| return; |
| } else if (element instanceof ITranslationUnit) { |
| postRefresh(element); |
| return; |
| } else if (element instanceof ICContainer) { |
| // if element itself has changed, not its children |
| if ((flags & ~(ICElementDelta.F_CHILDREN | ICElementDelta.F_FINE_GRAINED)) != 0) { |
| postRefresh(element); |
| } |
| } else if (element instanceof ArchiveContainer || element instanceof BinaryContainer) { |
| postContainerRefresh((IParent) element, element.getCProject()); |
| } |
| |
| } |
| |
| if (processResourceDeltas(delta.getResourceDeltas(), element)) |
| return; |
| |
| ICElementDelta[] affectedChildren = delta.getAffectedChildren(); |
| for (ICElementDelta element2 : affectedChildren) { |
| processDelta(element2); |
| } |
| } |
| |
| /** |
| * Processes resource deltas. |
| * |
| * @return true if the parent got refreshed |
| */ |
| private boolean processResourceDeltas(IResourceDelta[] deltas, Object parent) { |
| if (deltas == null) |
| return false; |
| |
| if (deltas.length > 1 && !(parent instanceof ICModel)) { |
| // more than one child changed, refresh from here downwards |
| // but not if the parent is ICModel |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=202085 |
| postRefresh(parent); |
| return true; |
| } |
| |
| for (IResourceDelta delta : deltas) { |
| if (processResourceDelta(delta, parent)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Processes a resource delta. |
| * |
| * @return true if the parent got refreshed |
| */ |
| private boolean processResourceDelta(IResourceDelta delta, Object parent) { |
| int status = delta.getKind(); |
| IResource resource = delta.getResource(); |
| // filter out changes affecting the output folder |
| if (resource == null) { |
| return false; |
| } |
| |
| // this could be optimized by handling all the added children in the parent |
| if ((status & IResourceDelta.REMOVED) != 0) { |
| postRemove(resource); |
| } |
| if ((status & IResourceDelta.ADDED) != 0) { |
| postAdd(parent, resource); |
| } |
| |
| int flags = delta.getFlags(); |
| // open/close state change of a project |
| if ((flags & IResourceDelta.OPEN) != 0) { |
| postProjectStateChanged(parent); |
| return true; |
| } |
| |
| processResourceDeltas(delta.getAffectedChildren(), resource); |
| return false; |
| } |
| |
| private boolean updateContainer(ICElement cfile) throws CModelException { |
| IParent container = null; |
| ICProject cproject = null; |
| if (cfile instanceof IBinary) { |
| IBinary bin = (IBinary) cfile; |
| if (bin.showInBinaryContainer()) { |
| cproject = bin.getCProject(); |
| container = cproject.getBinaryContainer(); |
| } |
| } else if (cfile instanceof IArchive) { |
| cproject = cfile.getCProject(); |
| container = cproject.getArchiveContainer(); |
| } |
| if (container != null) { |
| postContainerRefresh(container, cproject); |
| return true; |
| } |
| return false; |
| } |
| |
| // Tree refresh system |
| // We keep track of what we're going to refresh and avoid posting multiple refresh |
| // messages for the same elements. This avoids major performance issues where |
| // we update tree views hundreds or thousands of times. |
| protected interface IRefreshable { |
| public void refresh(); |
| } |
| |
| protected final class RefreshContainer implements IRefreshable { |
| private IParent container; |
| private Object project; |
| |
| public RefreshContainer(IParent container, Object project) { |
| this.container = container; |
| this.project = project; |
| } |
| |
| @Override |
| public void refresh() { |
| if (container instanceof IBinaryContainer || container instanceof IArchiveContainer) { |
| // Always refresh the project to properly show/hide container |
| fViewer.refresh(project); |
| } else if (container.hasChildren()) { |
| if (fViewer.testFindItem(container) != null) { |
| fViewer.refresh(container); |
| } else { |
| fViewer.refresh(project); |
| } |
| } else { |
| fViewer.refresh(project); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof RefreshContainer) { |
| RefreshContainer c = (RefreshContainer) o; |
| return c.container.equals(container) && c.project.equals(project); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return container.hashCode() * 10903143 + 31181; |
| } |
| } |
| |
| protected final class RefreshElement implements IRefreshable { |
| private Object element; |
| |
| public RefreshElement(Object element) { |
| this.element = element; |
| } |
| |
| @Override |
| public void refresh() { |
| if (element instanceof IWorkingCopy) { |
| if (fViewer.testFindItem(element) != null) { |
| fViewer.refresh(element); |
| } else { |
| fViewer.refresh(((IWorkingCopy) element).getOriginalElement()); |
| } |
| } else if (element instanceof IBinary) { |
| fViewer.refresh(element, true); |
| } else { |
| fViewer.refresh(element); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof RefreshElement) { |
| RefreshElement c = (RefreshElement) o; |
| return c.element.equals(element); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return element.hashCode() * 7 + 490487; |
| } |
| } |
| |
| protected final class RefreshProjectState implements IRefreshable { |
| private Object element; |
| |
| public RefreshProjectState(Object element) { |
| this.element = element; |
| } |
| |
| @Override |
| public void refresh() { |
| fViewer.refresh(element, true); |
| // trigger a syntetic selection change so that action refresh their |
| // enable state. |
| fViewer.setSelection(fViewer.getSelection()); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof RefreshElement) { |
| RefreshElement c = (RefreshElement) o; |
| return c.element.equals(element); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return element.hashCode() * 11 + 490487; |
| } |
| } |
| |
| protected void postContainerRefresh(final IParent container, final ICProject cproject) { |
| //System.out.println("UI Container:" + cproject + " " + container); |
| postRefreshable(new RefreshContainer(container, cproject)); |
| } |
| |
| protected void postRefresh(final Object element) { |
| //System.out.println("UI refresh:" + element); |
| postRefreshable(new RefreshElement(element)); |
| } |
| |
| protected void postAdd(final Object parent, final Object element) { |
| //System.out.println("UI add:" + parent + " " + element); |
| postRefreshable(new RefreshElement(parent)); |
| } |
| |
| protected void postRemove(final Object element) { |
| //System.out.println("UI remove:" + element); |
| postRefreshable(new RefreshElement(internalGetParent(element))); |
| } |
| |
| protected void postProjectStateChanged(final Object root) { |
| postRefreshable(new RefreshProjectState(root)); |
| } |
| |
| protected final void postRefreshable(final IRefreshable r) { |
| Control ctrl = fViewer.getControl(); |
| if (ctrl != null && !ctrl.isDisposed()) { |
| if (pendingRefreshes.contains(r)) |
| return; |
| pendingRefreshes.add(r); |
| ctrl.getDisplay().asyncExec(() -> { |
| pendingRefreshes.remove(r); |
| Control ctrl1 = fViewer.getControl(); |
| if (ctrl1 != null && !ctrl1.isDisposed()) { |
| r.refresh(); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public IRegion getSubject(ITextViewer textViewer, int offset) { |
| if (textViewer != null && fEditor != null) { |
| IRegion region = CWordFinder.findWord(textViewer.getDocument(), offset); |
| if (region != null) { |
| return region; |
| } |
| return new Region(offset, 0); |
| } |
| return null; |
| } |
| |
| @Override |
| public String getInformation(ITextViewer textViewer, IRegion subject) { |
| // deprecated API - not used anymore |
| Object info = getInformation2(textViewer, subject); |
| if (info != null) { |
| return info.toString(); |
| } |
| return null; |
| } |
| |
| @Override |
| public Object getInformation2(ITextViewer textViewer, IRegion subject) { |
| if (fEditor == null) |
| return null; |
| try { |
| ICElement element = SelectionConverter.getElementAtOffset(fEditor); |
| if (element != null) { |
| return element; |
| } |
| return SelectionConverter.getInput(fEditor); |
| } catch (CModelException e) { |
| return null; |
| } |
| } |
| } |