blob: d4c8e9dc1687cbf0607bc7cf19c25051ab4ececc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 itemis and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* itemis AG - initial API and implementation
* E.D.Willink - integration of XTFO code uder CQ 4866
*******************************************************************************/
package org.eclipse.ocl.examples.xtext.console.xtfo;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.swt.widgets.Display;
import org.eclipse.xtext.ide.editor.syntaxcoloring.IHighlightedPositionAcceptor;
import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator;
import org.eclipse.xtext.ide.editor.syntaxcoloring.MergingHighlightedPositionAcceptor;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.XtextSourceViewer;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.IXtextModelListener;
import org.eclipse.xtext.ui.editor.model.XtextDocument;
import org.eclipse.xtext.ui.editor.syntaxcoloring.AttributedPosition;
import org.eclipse.xtext.ui.editor.syntaxcoloring.HighlightingPresenter;
import org.eclipse.xtext.ui.editor.syntaxcoloring.ITextAttributeProvider;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import com.google.inject.Inject;
/**
* @author Sebastian Zarnekow - Initial contribution and API
*/
public class HighlightingReconciler implements ITextInputListener, IXtextModelListener, IHighlightedPositionAcceptor {
@Inject(optional=true)
private ISemanticHighlightingCalculator calculator;
@Inject
private ITextAttributeProvider attributeProvider;
/** The source viewer this highlighting reconciler is installed on */
private XtextSourceViewer sourceViewer;
/** The highlighting presenter */
private HighlightingPresenter presenter;
/** Background job's added highlighted positions */
private final List<AttributedPosition> addedPositions = new ArrayList<AttributedPosition>();
/** Background job's removed highlighted positions */
private List<AttributedPosition> removedPositions = new ArrayList<AttributedPosition>();
/** Number of removed positions */
private int removedPositionCount;
/**
* 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 reconciling = false;
/**
* Start reconciling positions.
*/
private void startReconcilingPositions() {
presenter.addAllPositions(removedPositions);
removedPositionCount = removedPositions.size();
}
/**
* Reconcile positions based on the AST subtrees
*
* @param subtrees
* the AST subtrees
*/
private void reconcilePositions(XtextResource resource) {
// for (int i= 0, n= subtrees.length; i < n; i++)
// subtrees[i].accept(fCollector);
MergingHighlightedPositionAcceptor acceptor = new MergingHighlightedPositionAcceptor(calculator);
acceptor.provideHighlightingFor(resource, this, CancelIndicator.NullImpl);
// calculator.provideHighlightingFor(resource, this);
List<AttributedPosition> oldPositions = removedPositions;
List<AttributedPosition> newPositions = new ArrayList<AttributedPosition>(removedPositionCount);
for (int i = 0, n = oldPositions.size(); i < n; i++) {
AttributedPosition current = oldPositions.get(i);
if (current != null)
newPositions.add(current);
}
removedPositions = newPositions;
}
/**
* Add a position with the given range and highlighting if it does not exist already.
* @param offset The range offset
* @param length The range length
*/
@Override
public void addPosition(int offset, int length, String... ids) {
TextAttribute highlighting = ids.length == 1 ?
attributeProvider.getAttribute(ids[0])
: attributeProvider.getMergedAttributes(ids);
boolean isExisting= false;
// TODO: use binary search
for (int i= 0, n= removedPositions.size(); i < n; i++) {
AttributedPosition position= removedPositions.get(i);
if (position == null)
continue;
if (position.isEqual(offset, length, highlighting)) {
isExisting= true;
removedPositions.set(i, null);
removedPositionCount--;
break;
}
}
if (!isExisting) {
AttributedPosition position= presenter.createHighlightedPosition(offset, length, highlighting);
addedPositions.add(position);
}
}
/**
* Update the presentation.
*
* @param textPresentation
* the text presentation
* @param addedPositions
* the added positions
* @param removedPositions
* the removed positions
*/
private void updatePresentation(TextPresentation textPresentation, List<AttributedPosition> addedPositions,
List<AttributedPosition> removedPositions) {
Runnable runnable = presenter.createUpdateRunnable(textPresentation, addedPositions, removedPositions);
if (runnable == null)
return;
Display display = getDisplay();
display.asyncExec(runnable);
}
private Display getDisplay() {
return this.sourceViewer.getControl().getDisplay();
}
/**
* Stop reconciling positions.
*/
private void stopReconcilingPositions() {
removedPositions.clear();
removedPositionCount = 0;
addedPositions.clear();
}
/**
* Install this reconciler on the given editor and presenter.
*
* @param sourceViewer
* the source viewer
* @param presenter
* the highlighting presenter
*/
public void install(XtextSourceViewer sourceViewer, HighlightingPresenter presenter) {
this.presenter = presenter;
this.sourceViewer = sourceViewer;
if (calculator != null) {
((IXtextDocument) sourceViewer.getDocument()).addModelListener(this);
sourceViewer.addTextInputListener(this);
}
refresh();
}
/**
* Uninstall this reconciler from the editor
*/
public void uninstall() {
if (presenter != null)
presenter.setCanceled(true);
if (sourceViewer.getDocument() != null) {
if (calculator != null) {
XtextDocument document = (XtextDocument) sourceViewer.getDocument();
document.removeModelListener(this);
sourceViewer.removeTextInputListener(this);
}
}
sourceViewer = null;
presenter = null;
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
*/
@Override
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
if (oldInput != null)
((IXtextDocument) oldInput).removeModelListener(this);
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
*/
@Override
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
if (newInput != null) {
refresh();
((IXtextDocument) newInput).addModelListener(this);
}
}
/**
* Refreshes the highlighting.
*/
public void refresh() {
if (calculator != null) {
((XtextDocument) sourceViewer.getDocument()).readOnly(new IUnitOfWork.Void<XtextResource>() {
@Override
public void process(@Nullable XtextResource state) throws Exception {
modelChanged(state);
}
});
} else {
Display display = getDisplay();
display.asyncExec(presenter.createSimpleUpdateRunnable());
}
}
@Override
public void modelChanged(XtextResource resource) {
// ensure at most one thread can be reconciling at any time
synchronized (fReconcileLock) {
if (reconciling)
return;
reconciling = true;
}
final HighlightingPresenter highlightingPresenter = presenter;
try {
if (highlightingPresenter == null)
return;
highlightingPresenter.setCanceled(false);
if (highlightingPresenter.isCanceled())
return;
startReconcilingPositions();
if (!highlightingPresenter.isCanceled()) {
reconcilePositions(resource);
}
final TextPresentation[] textPresentation = new TextPresentation[1];
if (!highlightingPresenter.isCanceled()) {
textPresentation[0] = highlightingPresenter.createPresentation(addedPositions, removedPositions);
}
if (!highlightingPresenter.isCanceled())
updatePresentation(textPresentation[0], addedPositions, removedPositions);
stopReconcilingPositions();
}
finally {
synchronized (fReconcileLock) {
reconciling = false;
}
}
}
public void setCalculator(ISemanticHighlightingCalculator calculator) {
this.calculator = calculator;
}
public ISemanticHighlightingCalculator getCalculator() {
return calculator;
}
}