/******************************************************************************* * Copyright (c) 2009, 2011 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 *******************************************************************************/ package org.eclipse.wst.sse.ui.internal.style; import java.util.ArrayList; 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.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.reconciler.DirtyRegion; import org.eclipse.jface.text.reconciler.IReconcilingStrategy; import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.wst.sse.core.internal.model.ModelManagerImpl; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.ui.ISemanticHighlighting; import org.eclipse.wst.sse.ui.ISemanticHighlightingExtension; import org.eclipse.wst.sse.ui.internal.Logger; import org.eclipse.wst.sse.ui.internal.style.SemanticHighlightingManager.HighlightedPosition; import org.eclipse.wst.sse.ui.internal.style.SemanticHighlightingManager.HighlightingStyle; /** * Semantic highlighting reconciler for Structured Source Editors. Based on * org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingReconciler * * @since 3.1 */ public class SemanticHighlightingReconciler implements IReconcilingStrategy, IReconcilingStrategyExtension { private IDocument fDocument; private ITextViewer fViewer; private SemanticHighlightingPresenter fPresenter; private ISemanticHighlighting[] fSemanticHighlightings; private HighlightingStyle[] fHighlightings; private List fAddedPositions = new ArrayList(); private List fRemovedPositions = new ArrayList(); /** Number of removed positions */ private 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(); private boolean fIsReconciling = false; /** The semantic highlighting presenter - cache for background thread, only valid during {@link #reconcile(IRegion)} */ private SemanticHighlightingPresenter fJobPresenter; /** Semantic highlightings - cache for background thread, only valid during {@link #reconcile(IRegion)} */ private ISemanticHighlighting[] fJobSemanticHighlightings; /** HighlightingStyle - cache for background thread, only valid during {@link #reconcile(IRegion)} */ private HighlightingStyle[] fJobHighlightings; private boolean fIsInstalled; public void install(ITextViewer sourceViewer, SemanticHighlightingPresenter presenter, ISemanticHighlighting[] semanticHighlightings, HighlightingStyle[] highlightings) { fViewer = sourceViewer; fPresenter = presenter; fSemanticHighlightings = semanticHighlightings; fHighlightings = highlightings; fIsInstalled = true; } public void uninstall() { fIsInstalled = false; fViewer = null; fPresenter = null; fSemanticHighlightings = null; fHighlightings = null; } public void reconcile(IRegion partition) { // ensure at most one thread can be reconciling at any time synchronized (fReconcileLock) { if (fIsReconciling) return; else fIsReconciling= true; } fJobPresenter = fPresenter; fJobSemanticHighlightings = fSemanticHighlightings; fJobHighlightings = fHighlightings; IStructuredModel model = null; try { if (fJobPresenter == null || fJobSemanticHighlightings == null || fJobHighlightings == null || fDocument == null) return; fJobPresenter.setCanceled(false); startReconcilingPositions(); IStructuredDocument document = (IStructuredDocument) fDocument; model = ModelManagerImpl.getInstance().getModelForRead(document); IStructuredDocumentRegion[] regions = document.getStructuredDocumentRegions(partition.getOffset(), partition.getLength()); for (int i = 0; i < regions.length && fIsInstalled; i++) { if (document.containsReadOnly(regions[i].getStartOffset(), regions[i].getLength())) addPosition(new Position(regions[i].getStartOffset(), regions[i].getLength()), null, true); else { for (int j = 0; j < fJobSemanticHighlightings.length && fIsInstalled; j++) { if (fJobHighlightings[j].isEnabled()) { Position[] consumes = null; if (fJobSemanticHighlightings[j] instanceof ISemanticHighlightingExtension && model != null) { consumes = ((ISemanticHighlightingExtension) fJobSemanticHighlightings[j]).consumes(regions[i], model.getIndexedRegion(regions[i].getStartOffset())); } else { consumes = fJobSemanticHighlightings[j].consumes(regions[i]); } if (consumes != null) { for (int k = 0; k < consumes.length; k++) addPosition(consumes[k], fJobHighlightings[j]); } } } } } if (fIsInstalled) { List oldPositions = fRemovedPositions; List newPositions = new ArrayList(fNOfRemovedPositions); for (int i = 0, n = oldPositions.size(); i < n && fIsInstalled; i++) { Object current = oldPositions.get(i); if (current != null) newPositions.add(current); } fRemovedPositions = newPositions; TextPresentation presentation = null; if (!fJobPresenter.isCanceled()) presentation = fJobPresenter.createPresentation(fAddedPositions, fRemovedPositions); if (!fJobPresenter.isCanceled()) updatePresentation(presentation, fAddedPositions, fRemovedPositions); } stopReconcilingPositions(); } finally { fJobPresenter= null; fJobSemanticHighlightings= null; fJobHighlightings= null; if (model != null) model.releaseFromRead(); synchronized (fReconcileLock) { fIsReconciling= false; } } } private void addPosition(Position position, HighlightingStyle highlighting) { addPosition(position, highlighting, false); } private void addPosition(Position position, HighlightingStyle highlighting, boolean isReadOnly) { boolean isExisting = false; // TODO: use binary search for (int i = 0, n = fRemovedPositions.size(); i < n; i++) { HighlightedPosition highlightedPosition = (HighlightedPosition) fRemovedPositions.get(i); if (highlightedPosition == null) continue; if (highlightedPosition.isEqual(position, highlighting)) { isExisting = true; fRemovedPositions.set(i, null); fNOfRemovedPositions--; break; } } if (!isExisting) { fAddedPositions.add(fJobPresenter.createHighlightedPosition(position, highlighting, isReadOnly)); } } /** * Update the presentation. * * @param textPresentation * the text presentation * @param addedPositions * the added positions * @param removedPositions * the removed positions */ private void updatePresentation(TextPresentation textPresentation, List addedPositions, List removedPositions) { Runnable runnable = fJobPresenter.createUpdateRunnable(textPresentation, addedPositions, removedPositions); if (runnable == null) return; if (fViewer == null) return; Control viewerControl = fViewer.getTextWidget(); if (viewerControl == null) return; Display display = viewerControl.getDisplay(); if (display == null || display.isDisposed()) return; display.asyncExec(runnable); } /** * Start reconciling positions. */ private void startReconcilingPositions() { fJobPresenter.addAllPositions(fRemovedPositions); fNOfRemovedPositions = fRemovedPositions.size(); } /** * Stop reconciling positions. */ private void stopReconcilingPositions() { fRemovedPositions.clear(); fNOfRemovedPositions = 0; fAddedPositions.clear(); } public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { reconcile(dirtyRegion); } public void setDocument(IDocument document) { fDocument = (document instanceof IStructuredDocument) ? document : null; } public void initialReconcile() { // Do nothing } public void setProgressMonitor(IProgressMonitor monitor) { } /** * Schedule a background job for reconciling the Semantic Highlighting model. */ private void scheduleJob() { synchronized (fJobLock) { final Job oldJob= fJob; if (fJob != null) { fJob.cancel(); fJob= null; } fJob= new Job("Semantic Highlighting Job") { protected IStatus run(IProgressMonitor monitor) { if (oldJob != null) { try { oldJob.join(); } catch (InterruptedException e) { Logger.logException(e); return Status.CANCEL_STATUS; } } if (monitor.isCanceled()) return Status.CANCEL_STATUS; reconcile(new Region(0, fDocument.getLength())); synchronized (fJobLock) { // allow the job to be gc'ed if (fJob == this) fJob= null; } return Status.OK_STATUS; } }; fJob.setSystem(true); fJob.setPriority(Job.DECORATE); fJob.schedule(); } } public void refresh() { if (fDocument != null) scheduleJob(); } }