blob: abc4c5bd20deb31c0b717f44850c66a4a7ef6628 [file] [log] [blame]
/*
* 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);
}
}