/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.internal.text.editor; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Uninterruptibles; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor; import com.google.dart.engine.utilities.source.SourceRange; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.DartUI; import com.google.dart.tools.ui.internal.text.dart.IDartReconcilingListener; import com.google.dart.tools.ui.internal.text.editor.SemanticHighlightingManager.HighlightedPosition; import com.google.dart.tools.ui.internal.text.editor.SemanticHighlightingManager.Highlighting; 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.ITextInputListener; 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 java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.TimeUnit; /** * Semantic highlighting reconciler - Background thread implementation. */ public class SemanticHighlightingReconciler implements IDartReconcilingListener, ITextInputListener { /** * Collects positions from the AST. */ private class PositionCollector extends GeneralizingAstVisitor<Void> { /** * Cache tokens for performance. */ private final SemanticToken token = new SemanticToken(); @Override public Void visitNode(AstNode node) { processNode(token, node); return super.visitNode(node); } /** * Add 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, Highlighting highlighting) { boolean isExisting = false; Position[] positions = removedPositions; int left = 0; int right = positions.length - 1; int oldMid = -1; // Do a binary search through the positions array, looking for the given offset. while (left <= right) { int mid; // Choose a mid. if (positions[left].getOffset() == offset) { mid = left; } else if (positions[right].getOffset() == offset) { mid = right; } else { mid = (left + right) / 2; } if (oldMid == mid) { // If we didn't make any progress, exit the search. break; } else { oldMid = mid; } if (offset > positions[mid].getOffset()) { left = mid; } else if (offset < positions[mid].getOffset()) { right = mid; } else { int index = mid; // We found offset. Back up until we reach the first instance of that offset. while (index > 0 && positions[index - 1].getOffset() == offset) { index--; } // Check to see if we want to keep all the positions which equal offset. while (index < positions.length && positions[index].getOffset() == offset) { if (!removedPositionsDeleted[index]) { HighlightedPosition position = (HighlightedPosition) positions[index]; if (position.isEqual(offset, length, highlighting)) { isExisting = true; removedPositionsDeleted[index] = true; fNOfRemovedPositions--; break; } } index++; } break; } } // Older (and much simpler) O(n^2) algorithm for updating removedPositions. // for (int i = 0, n = removedPositions.length; i < n; i++) { // if (removedPositionsDeleted[i]) { // continue; // } // // HighlightedPosition position = (HighlightedPosition) removedPositions[i]; // // if (position.isEqual(offset, length, highlighting)) { // isExisting = true; // removedPositionsDeleted[i] = true; // fNOfRemovedPositions--; // break; // } // } if (!isExisting) { fAddedPositions.add(fJobPresenter.createHighlightedPosition(offset, length, highlighting)); } } } /** Position collector */ private final PositionCollector fCollector = new PositionCollector(); private Comparator<Position> positionsComparator = new Comparator<Position>() { @Override public int compare(Position position1, Position position2) { return position1.offset - position2.offset; } }; /** The Dart editor on which this semantic highlighting reconciler is installed */ private DartEditor fEditor; /** The source viewer this semantic highlighting reconciler is installed on */ private ISourceViewer fSourceViewer; /** The semantic highlighting presenter */ private SemanticHighlightingPresenter fPresenter; /** Semantic highlightings */ private SemanticHighlighting[] fSemanticHighlightings; /** Highlightings */ private Highlighting[] fHighlightings; /** Background job's added highlighted positions */ private final List<Position> fAddedPositions = new ArrayList<Position>(); /** Background job's removed highlighted positions */ private List<Position> fRemovedPositions = new ArrayList<Position>(); private Position[] removedPositions; private boolean[] removedPositionsDeleted; /** 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(); /** * The semantic highlighting presenter - cache for background thread, only valid during * {@link #reconciled(boolean, IProgressMonitor)} */ private SemanticHighlightingPresenter fJobPresenter; /** * Semantic highlightings - cache for background thread, only valid during * {@link #reconciled(boolean, IProgressMonitor)} */ private SemanticHighlighting[] fJobSemanticHighlightings; /** * Highlightings - cache for background thread, only valid during * {@link #reconciled(boolean, IProgressMonitor)} */ private Highlighting[] fJobHighlightings; @Override public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { synchronized (fJobLock) { if (fJob != null) { fJob.cancel(); fJob = null; } } } @Override public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { if (newInput != null) { scheduleJob(); } } /** * Install 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(DartEditor editor, DartSourceViewer sourceViewer, SemanticHighlightingPresenter presenter, SemanticHighlighting[] semanticHighlightings, Highlighting[] highlightings) { fPresenter = presenter; fSemanticHighlightings = semanticHighlightings; fHighlightings = highlightings; fEditor = editor; fSourceViewer = sourceViewer; if (fEditor instanceof CompilationUnitEditor) { ((CompilationUnitEditor) fEditor).addReconcileListener(this); scheduleJob(); } else if (fEditor == null) { fSourceViewer.addTextInputListener(this); scheduleJob(); } } @Override public void reconciled(CompilationUnit ast) { synchronized (fReconcileLock) { fJobPresenter = fPresenter; fJobSemanticHighlightings = fSemanticHighlightings; fJobHighlightings = fHighlightings; try { if (fJobPresenter == null || fJobSemanticHighlightings == null || fJobHighlightings == null) { return; } fJobPresenter.setCanceled(false); startReconcilingPositions(); if (!fJobPresenter.isCanceled()) { reconcilePositions(ast); } 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; } } } /** * Refreshes the highlighting. */ public void refresh() { scheduleJob(); } /** * Uninstall this reconciler from the editor */ public void uninstall() { if (fPresenter != null) { fPresenter.setCanceled(true); } if (fEditor != null) { if (fEditor instanceof CompilationUnitEditor) { ((CompilationUnitEditor) fEditor).removeReconcileListener(this); } else { fSourceViewer.removeTextInputListener(this); } fEditor = null; } fSourceViewer = null; fSemanticHighlightings = null; fHighlightings = null; fPresenter = null; } private boolean canReconcilePositions() { ISourceViewer viewer = fSourceViewer; if (viewer == null) { return false; } IDocument document = viewer.getDocument(); return !DartUI.isTooComplexDartDocument(document); } private final void processNode(SemanticToken token, AstNode node) { ISourceViewer sourceViewer = this.fSourceViewer; if (sourceViewer == null) { return; } IDocument document = sourceViewer.getDocument(); if (document == null) { return; } // update token token.update(node); token.attachSource(document); // try SemanticHighlighting instances for (int i = 0, n = fJobSemanticHighlightings.length; i < n; i++) { if (fJobHighlightings[i].isEnabled()) { SemanticHighlighting semanticHighlighting = fJobSemanticHighlightings[i]; // try multiple positions { List<SourceRange> ranges = semanticHighlighting.consumesMulti(token); if (ranges != null) { for (SourceRange range : ranges) { int offset = range.getOffset(); int length = range.getLength(); if (offset > -1 && length > 0) { fCollector.addPosition(offset, length, fJobHighlightings[i]); } } break; } } // try single position boolean consumes; if (node instanceof SimpleIdentifier) { consumes = semanticHighlighting.consumesIdentifier(token); } else { consumes = semanticHighlighting.consumes(token); } if (consumes) { int offset = node.getOffset(); int length = node.getLength(); if (offset > -1 && length > 0) { fCollector.addPosition(offset, length, fJobHighlightings[i]); } break; } } } token.clear(); } /** * Reconcile positions based on the AST subtrees * * @param subtrees the AST subtrees */ private void reconcilePositions(CompilationUnit unit) { // copy fRemovedPositions into removedPositions and removedPositionsDeleted removedPositions = fRemovedPositions.toArray(new Position[fRemovedPositions.size()]); Arrays.sort(removedPositions, positionsComparator); removedPositionsDeleted = new boolean[removedPositions.length]; if (canReconcilePositions()) { unit.accept(fCollector); } // copy removedPositions and removedPositionsDeleted into fRemovedPositions fRemovedPositions = new ArrayList<Position>(removedPositions.length); for (int i = 0; i < removedPositions.length; i++) { if (!removedPositionsDeleted[i]) { fRemovedPositions.add(removedPositions[i]); } } List<Position> oldPositions = fRemovedPositions; List<Position> newPositions = new ArrayList<Position>(fNOfRemovedPositions); for (int i = 0, n = oldPositions.size(); i < n; i++) { Position current = oldPositions.get(i); if (current != null) { newPositions.add(current); } } fRemovedPositions = newPositions; Collections.sort(fRemovedPositions, positionsComparator); Collections.sort(fAddedPositions, positionsComparator); } /** * Schedule a background job for retrieving the AST and reconciling the Semantic Highlighting * model. */ private void scheduleJob() { // final DartElement element = fEditor.getInputDartElement(); synchronized (fJobLock) { final Job oldJob = fJob; if (fJob != null) { fJob.cancel(); fJob = null; } fJob = new Job(DartEditorMessages.SemanticHighlighting_job) { @Override protected IStatus run(IProgressMonitor monitor) { if (oldJob != null) { try { oldJob.join(); } catch (InterruptedException e) { DartToolsPlugin.log(e); return Status.CANCEL_STATUS; } } if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } // prepare CompilationUnit CompilationUnit unit = null; { DartEditor editor = fEditor; if (editor == null) { return Status.CANCEL_STATUS; } while (!monitor.isCanceled()) { unit = editor.getInputUnit(); if (unit != null) { break; } Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); } } // if has CompilationUnit, do reconcile if (unit != null) { reconciled(unit); } // done 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(); } } /** * Start reconciling positions. */ private void startReconcilingPositions() { fJobPresenter.addAllPositions(fRemovedPositions); fNOfRemovedPositions = fRemovedPositions.size(); } /** * Stop reconciling positions. */ private void stopReconcilingPositions() { fRemovedPositions.clear(); fNOfRemovedPositions = 0; fAddedPositions.clear(); } /** * Update the presentation. * * @param textPresentation the text presentation * @param addedPositions the added positions * @param removedPositions the removed positions */ private void updatePresentation(TextPresentation textPresentation, List<Position> addedPositions, List<Position> removedPositions) { Runnable runnable = fJobPresenter.createUpdateRunnable( textPresentation, addedPositions, removedPositions); if (runnable == null) { return; } DartEditor 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); addedPositions = Lists.newArrayList(addedPositions); removedPositions = Lists.newArrayList(removedPositions); fJobPresenter.updatePresentation(display, addedPositions, removedPositions); } }