/******************************************************************************* * Copyright (c) 2000, 2014 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Anton Leherbauer (Wind River Systems) - Adapted for CDT * Markus Schorn (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; import org.eclipse.cdt.core.dom.ast.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTImageLocation; import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTClassVirtSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVirtSpecifier; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ILanguage; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.internal.core.dom.parser.ASTNode; import org.eclipse.cdt.internal.core.model.ASTCache; import org.eclipse.cdt.internal.core.parser.scanner.ASTPreprocessorName; import org.eclipse.cdt.internal.ui.editor.SemanticHighlightingManager.HighlightedPosition; import org.eclipse.cdt.internal.ui.editor.SemanticHighlightingManager.HighlightingStyle; import org.eclipse.cdt.internal.ui.text.ICReconcilingListener; /** * Semantic highlighting reconciler - Background thread implementation. * Cloned from JDT. * * @since 4.0 */ public class SemanticHighlightingReconciler implements ICReconcilingListener { private class PositionCollectorRequirements { public boolean visitImplicitNames = false; public boolean visitExpressions = false; } /** * Collects positions from the AST. */ private class PositionCollector extends ASTVisitor { /** The semantic token */ private SemanticToken fToken= new SemanticToken(); public PositionCollector(PositionCollectorRequirements requirements) { shouldVisitTranslationUnit= true; shouldVisitNames= true; shouldVisitDeclarations= true; shouldVisitExpressions= requirements.visitExpressions; shouldVisitStatements= true; shouldVisitDeclarators= true; shouldVisitNamespaces= true; shouldVisitVirtSpecifiers= true; shouldVisitImplicitNames = requirements.visitImplicitNames; shouldVisitImplicitNameAlternates = requirements.visitImplicitNames; } @Override public int visit(IASTTranslationUnit tu) { // Visit macro definitions. IASTPreprocessorMacroDefinition[] macroDefs= tu.getMacroDefinitions(); for (IASTPreprocessorMacroDefinition macroDef : macroDefs) { if (macroDef.isPartOfTranslationUnitFile()) { visitNode(macroDef.getName()); } } // Visit macro expansions. IASTPreprocessorMacroExpansion[] macroExps= tu.getMacroExpansions(); for (IASTPreprocessorMacroExpansion macroExp : macroExps) { if (macroExp.isPartOfTranslationUnitFile()) { IASTName macroRef= macroExp.getMacroReference(); visitNode(macroRef); IASTName[] nestedMacroRefs= macroExp.getNestedMacroReferences(); for (IASTName nestedMacroRef : nestedMacroRefs) { visitNode(nestedMacroRef); } } } // Visit ordinary code. return super.visit(tu); } @Override public int visit(IASTDeclaration declaration) { if (!declaration.isPartOfTranslationUnitFile()) { return PROCESS_SKIP; } return PROCESS_CONTINUE; } @Override public int leave(IASTDeclaration declaration) { // if (!shouldVisitCatchHandlers && declaration instanceof IASTFunctionDefinition) { // shouldVisitCatchHandlers= true; // IASTFunctionDefinition functionDef= (IASTFunctionDefinition) declaration; // ICPPASTFunctionTryBlockDeclarator declarator= (ICPPASTFunctionTryBlockDeclarator) functionDef.getDeclarator(); // ICPPASTCatchHandler[] catchHandlers= declarator.getCatchHandlers(); // for (ICPPASTCatchHandler catchHandler : catchHandlers) { // catchHandler.accept(this); // } // } return PROCESS_CONTINUE; } @Override public int visit(ICPPASTNamespaceDefinition namespace) { if (!namespace.isPartOfTranslationUnitFile()) { return PROCESS_SKIP; } return PROCESS_CONTINUE; } @Override public int visit(IASTDeclarator declarator) { // if (declarator instanceof ICPPASTFunctionTryBlockDeclarator) { // shouldVisitCatchHandlers= false; // } return PROCESS_CONTINUE; } @Override public int visit(IASTStatement statement) { // if (!shouldVisitCatchHandlers && statement instanceof ICPPASTCatchHandler) { // return PROCESS_SKIP; // } return PROCESS_CONTINUE; } @Override public int visit(IASTName name) { if (visitNode(name)) { return PROCESS_SKIP; } return PROCESS_CONTINUE; } @Override public int visit(ICPPASTVirtSpecifier virtSpecifier) { visitNode(virtSpecifier); return PROCESS_CONTINUE; } @Override public int visit(ICPPASTClassVirtSpecifier classVirtSpecifier) { visitNode(classVirtSpecifier); return PROCESS_CONTINUE; } @Override public int visit(IASTExpression expression) { if (visitNode(expression)) { return PROCESS_SKIP; } return PROCESS_CONTINUE; } private boolean visitNode(IASTNode node) { boolean consumed= false; fToken.update(node); for (int i= 0, n= fJobSemanticHighlightings.length; i < n; ++i) { SemanticHighlighting semanticHighlighting= fJobSemanticHighlightings[i]; // If the semantic highlighting doesn't color expressions, don't bother // passing it one to begin with. if (node instanceof IASTExpression && !semanticHighlighting.requiresExpressions()) { continue; } if (fJobHighlightings[i].isEnabled() && semanticHighlighting.consumes(fToken)) { IASTNodeLocation location = getLocationToHighlight(node); if (location != null) { highlightLocation(location, fJobHighlightings[i]); } consumed= true; break; } } fToken.clear(); return consumed; } /** * Gets the location to highlight for a given node. * * @param node the node */ private IASTNodeLocation getLocationToHighlight(IASTNode node) { IASTImageLocation imageLocation = null; if (node instanceof IASTName) { imageLocation = ((IASTName) node).getImageLocation(); } else if (node instanceof ASTNode) { // Non-names can still have image locations, they're just not exposed in IASTNode. imageLocation = ((ASTNode) node).getImageLocation(); } if (imageLocation != null) { if (imageLocation.getLocationKind() != IASTImageLocation.MACRO_DEFINITION) { return imageLocation; } } else { // Fallback in case no image location available. // Only use the fallback for nodes that are not preprocessor nodes, // because in the case of nested macro expansions, a preprocessor node // can have a node location that is not representative of its actual // image; such nodes should have an image location (accessed via // getImageLocation(), above) where appropriate. if (!(node instanceof ASTPreprocessorName)) { IASTNodeLocation[] nodeLocations= node.getNodeLocations(); if (nodeLocations.length == 1) { IASTNodeLocation nodeLocation = nodeLocations[0]; if (!(nodeLocation instanceof IASTMacroExpansionLocation)) { return nodeLocation; } } } } return null; } /** * Highlights the given node location with the given highlighting. * * @param nodeLocation the node location to highlight * @param highlightingStyle the highlighting style to apply */ private void highlightLocation(IASTNodeLocation nodeLocation, HighlightingStyle highlightingStyle) { int offset= nodeLocation.getNodeOffset(); int length= nodeLocation.getNodeLength(); if (offset > -1 && length > 0) { addPosition(offset, length, highlightingStyle); } } /** * Adds a position with the given range and highlighting iff it does not exist already. * * @param offset The range offset * @param length The range length * @param highlighting The highlighting */ private void addPosition(int offset, int length, HighlightingStyle highlightingStyle) { boolean isExisting= false; // TODO: use binary search for (int i= 0, n= fRemovedPositions.size(); i < n; i++) { HighlightedPosition position= fRemovedPositions.get(i); if (position == null) continue; if (position.isEqual(offset, length, highlightingStyle)) { isExisting= true; fRemovedPositions.set(i, null); fNOfRemovedPositions--; break; } } if (!isExisting) { HighlightedPosition position= fJobPresenter.createHighlightedPosition(offset, length, highlightingStyle); fAddedPositions.add(position); } } } /** The C editor this semantic highlighting reconciler is installed on */ private CEditor fEditor; /** The semantic highlighting presenter */ protected SemanticHighlightingPresenter fPresenter; /** Semantic highlightings */ protected SemanticHighlighting[] fSemanticHighlightings; /** Highlightings */ private HighlightingStyle[] fHighlightings; /** Background job's added highlighted positions */ protected List<HighlightedPosition> fAddedPositions= new ArrayList<HighlightedPosition>(); /** Background job's removed highlighted positions */ protected List<HighlightedPosition> fRemovedPositions= new ArrayList<HighlightedPosition>(); /** Number of removed positions */ protected int fNOfRemovedPositions; /** Background job */ private Job fJob; /** Background job lock */ private final Object fJobLock= new Object(); /** Reconcile operation lock. */ private final Object fReconcileLock= new Object(); /** * <code>true</code> if any thread is executing * <code>reconcile</code>, <code>false</code> otherwise. */ private boolean fIsReconciling= false; /** * The semantic highlighting presenter - cache for background thread, only valid during * {@link #reconciled(IASTTranslationUnit, boolean, IProgressMonitor)} */ protected SemanticHighlightingPresenter fJobPresenter; /** * Semantic highlightings - cache for background thread, only valid during * {@link #reconciled(IASTTranslationUnit, boolean, IProgressMonitor)} */ protected SemanticHighlighting[] fJobSemanticHighlightings; /** * Highlightings - cache for background thread, only valid during * {@link #reconciled(IASTTranslationUnit, boolean, IProgressMonitor)} */ private HighlightingStyle[] fJobHighlightings; @Override public void aboutToBeReconciled() { // Do nothing } @Override public void reconciled(IASTTranslationUnit ast, boolean force, IProgressMonitor progressMonitor) { // Ensure at most one thread can be reconciling at any time. synchronized (fReconcileLock) { if (fIsReconciling) return; fIsReconciling= true; } fJobPresenter= fPresenter; fJobSemanticHighlightings= fSemanticHighlightings; fJobHighlightings= fHighlightings; try { if (fJobPresenter == null || fJobSemanticHighlightings == null || fJobHighlightings == null) return; fJobPresenter.setCanceled(progressMonitor != null && progressMonitor.isCanceled()); if (ast == null || fJobPresenter.isCanceled()) return; PositionCollector collector= new PositionCollector(getRequirements()); startReconcilingPositions(); if (!fJobPresenter.isCanceled()) reconcilePositions(ast, collector); TextPresentation textPresentation= null; if (!fJobPresenter.isCanceled()) textPresentation= fJobPresenter.createPresentation(fAddedPositions, fRemovedPositions); if (!fJobPresenter.isCanceled()) updatePresentation(textPresentation, fAddedPositions, fRemovedPositions); stopReconcilingPositions(); } finally { fJobPresenter= null; fJobSemanticHighlightings= null; fJobHighlightings= null; synchronized (fReconcileLock) { fIsReconciling= false; } } } private PositionCollectorRequirements getRequirements() { PositionCollectorRequirements result = new PositionCollectorRequirements(); for (int i = 0; i < fSemanticHighlightings.length; i++) { SemanticHighlighting sh = fSemanticHighlightings[i]; if (fHighlightings[i].isEnabled()) { if (sh.requiresImplicitNames()) { result.visitImplicitNames = true; } if (sh.requiresExpressions()) { result.visitExpressions = true; } } } return result; } /** * Starts reconciling positions. */ protected void startReconcilingPositions() { fJobPresenter.addAllPositions(fRemovedPositions); fNOfRemovedPositions= fRemovedPositions.size(); } /** * Reconciles positions based on the AST. * * @param ast the AST * @param visitor the AST visitor */ private void reconcilePositions(IASTTranslationUnit ast, PositionCollector visitor) { ast.accept(visitor); List<HighlightedPosition> oldPositions= fRemovedPositions; List<HighlightedPosition> newPositions= new ArrayList<HighlightedPosition>(fNOfRemovedPositions); for (int i= 0, n= oldPositions.size(); i < n; i ++) { HighlightedPosition current= oldPositions.get(i); if (current != null) newPositions.add(current); } fRemovedPositions= newPositions; // Positions need to be sorted by ascending offset Collections.sort(fAddedPositions, new Comparator<Position>() { @Override public int compare(final Position p1, final Position p2) { return p1.getOffset() - p2.getOffset(); } }); } /** * Updates the presentation. * * @param textPresentation the text presentation * @param addedPositions the added positions * @param removedPositions the removed positions */ protected void updatePresentation(TextPresentation textPresentation, List<HighlightedPosition> addedPositions, List<HighlightedPosition> removedPositions) { Runnable runnable= fJobPresenter.createUpdateRunnable(textPresentation, addedPositions, removedPositions); if (runnable == null) return; CEditor editor= fEditor; if (editor == null) return; IWorkbenchPartSite site= editor.getSite(); if (site == null) return; Shell shell= site.getShell(); if (shell == null || shell.isDisposed()) return; Display display= shell.getDisplay(); if (display == null || display.isDisposed()) return; display.asyncExec(runnable); } /** * Stops reconciling positions. */ protected void stopReconcilingPositions() { fRemovedPositions.clear(); fNOfRemovedPositions= 0; fAddedPositions.clear(); } /** * Installs this reconciler on the given editor, presenter and highlightings. * @param editor the editor * @param sourceViewer the source viewer * @param presenter the semantic highlighting presenter * @param semanticHighlightings the semantic highlightings * @param highlightings the highlightings */ public void install(CEditor editor, ISourceViewer sourceViewer, SemanticHighlightingPresenter presenter, SemanticHighlighting[] semanticHighlightings, HighlightingStyle[] highlightings) { fPresenter= presenter; fSemanticHighlightings= semanticHighlightings; fHighlightings= highlightings; fEditor= editor; if (fEditor != null) { fEditor.addReconcileListener(this); } } /** * Uninstalls this reconciler from the editor */ public void uninstall() { if (fPresenter != null) fPresenter.setCanceled(true); if (fEditor != null) { fEditor.removeReconcileListener(this); fEditor= null; } fSemanticHighlightings= null; fHighlightings= null; fPresenter= null; } /** * Schedules a background job for retrieving the AST and reconciling the Semantic Highlighting model. */ private void scheduleJob() { final ICElement element= fEditor.getInputCElement(); synchronized (fJobLock) { final Job oldJob= fJob; if (fJob != null) { fJob.cancel(); fJob= null; } if (element != null) { fJob= new Job(CEditorMessages.SemanticHighlighting_job) { @Override protected IStatus run(final IProgressMonitor monitor) { if (oldJob != null) { try { oldJob.join(); } catch (InterruptedException e) { CUIPlugin.log(e); return Status.CANCEL_STATUS; } } if (monitor.isCanceled()) return Status.CANCEL_STATUS; final Job me= this; ASTProvider astProvider= CUIPlugin.getDefault().getASTProvider(); IStatus status= astProvider.runOnAST(element, ASTProvider.WAIT_IF_OPEN, monitor, new ASTCache.ASTRunnable() { @Override public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) { reconciled(ast, true, monitor); synchronized (fJobLock) { // allow the job to be gc'ed if (fJob == me) fJob= null; } return Status.OK_STATUS; } }); return status; } }; // fJob.setSystem(true); fJob.setPriority(Job.SHORT); fJob.schedule(); } } } /** * Refreshes the highlighting. */ public void refresh() { scheduleJob(); } }