/*******************************************************************************
* Copyright (c) 2011 Sebastian Benz 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:
* Sebastian Benz - initial API and implementation
*******************************************************************************/
package org.xrepl.ui.embedded;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.projection.IProjectionListener;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextSyntaxDiagnostic;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.folding.FoldedPosition;
import org.eclipse.xtext.ui.editor.folding.IFoldingRegionProvider;
import org.eclipse.xtext.ui.editor.folding.IFoldingStructureProvider;
import org.eclipse.xtext.ui.editor.model.IXtextModelListener;
import com.google.common.base.Predicate;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
/**
* Default implementation of interface {@link IFoldingStructureProvider}
*
* @author Michael Clay - Initial contribution and API
* @author Mikael Barbero - Dependant of Embedded XtextEditor
*/
public class EmbeddedFoldingStructureProvider implements IXtextModelListener {
@Inject
private IFoldingRegionProvider foldingRegionProvider;
private EmbeddedXtextEditor editor;
private ProjectionViewer viewer;
private ProjectionChangeListener projectionListener;
public void install(EmbeddedXtextEditor editor, ProjectionViewer viewer) {
Assert.isNotNull(editor);
Assert.isNotNull(viewer);
uninstall();
this.editor = editor;
this.viewer = viewer;
projectionListener = new ProjectionChangeListener(viewer);
}
public void initialize() {
calculateProjectionAnnotationModel(false);
}
public void uninstall() {
if (isInstalled()) {
handleProjectionDisabled();
projectionListener.dispose();
projectionListener = null;
editor = null;
}
}
/**
* Returns <code>true</code> if the provider is installed,
* <code>false</code> otherwise.
*
* @return <code>true</code> if the provider is installed,
* <code>false</code> otherwise
*/
protected final boolean isInstalled() {
return editor != null;
}
/**
* @see org.eclipse.xtext.ui.editor.model.IXtextModelListener#modelChanged(org.eclipse.xtext.resource.XtextResource)
*/
public void modelChanged(XtextResource resource) {
boolean existingSyntaxErrors = Iterables.any(resource.getErrors(),
new Predicate<Diagnostic>() {
public boolean apply(Diagnostic diagnostic) {
return diagnostic instanceof XtextSyntaxDiagnostic;
}
});
if (!existingSyntaxErrors) {
calculateProjectionAnnotationModel(false);
}
}
protected void handleProjectionEnabled() {
handleProjectionDisabled();
if (isInstalled()) {
initialize();
editor.getDocument().addModelListener(this);
}
}
protected void handleProjectionDisabled() {
if (editor.getDocument() != null) {
editor.getDocument().removeModelListener(this);
}
}
protected void calculateProjectionAnnotationModel(boolean allowCollapse) {
ProjectionAnnotationModel projectionAnnotationModel = this.viewer
.getProjectionAnnotationModel();
if (projectionAnnotationModel != null) {
Collection<FoldedPosition> foldingRegions = foldingRegionProvider
.getFoldingRegions(editor.getDocument());
HashBiMap<Position, FoldedPosition> positionsMap = toPositionIndexedMap(foldingRegions);
Annotation[] newRegions = mergeFoldingRegions(positionsMap,
projectionAnnotationModel);
updateFoldingRegions(allowCollapse, projectionAnnotationModel,
positionsMap, newRegions);
}
}
protected HashBiMap<Position, FoldedPosition> toPositionIndexedMap(
Collection<FoldedPosition> foldingRegions) {
HashBiMap<Position, FoldedPosition> positionsMap = HashBiMap.create();
for (FoldedPosition foldingRegion : foldingRegions) {
positionsMap.put(toPosition(foldingRegion), foldingRegion);
}
return positionsMap;
}
@SuppressWarnings("unchecked")
protected Annotation[] mergeFoldingRegions(
HashBiMap<Position, FoldedPosition> positionsMap,
ProjectionAnnotationModel projectionAnnotationModel) {
List<Annotation> deletions = new ArrayList<Annotation>();
for (Iterator<Annotation> iterator = projectionAnnotationModel
.getAnnotationIterator(); iterator.hasNext();) {
Annotation annotation = iterator.next();
if (annotation instanceof ProjectionAnnotation) {
Position position = projectionAnnotationModel
.getPosition(annotation);
if (positionsMap.remove(position) == null) {
deletions.add(annotation);
}
}
}
return deletions.toArray(new Annotation[deletions.size()]);
}
protected void updateFoldingRegions(boolean allowCollapse,
ProjectionAnnotationModel model,
HashBiMap<Position, FoldedPosition> positionsMap,
Annotation[] deletions) {
Map<ProjectionAnnotation, Position> additionsMap = new HashMap<ProjectionAnnotation, Position>();
for (Iterator<FoldedPosition> iterator = positionsMap.values()
.iterator(); iterator.hasNext();) {
FoldedPosition foldingRegion = iterator.next();
addProjectionAnnotation(allowCollapse, foldingRegion, additionsMap);
}
if (deletions.length != 0 || additionsMap.size() != 0) {
model.modifyAnnotations(deletions, additionsMap,
new Annotation[] {});
}
}
protected void addProjectionAnnotation(boolean allowCollapse,
FoldedPosition foldingRegion,
Map<ProjectionAnnotation, Position> additionsMap) {
ProjectionAnnotation projectionAnnotation = createProjectionAnnotation(
allowCollapse, foldingRegion);
additionsMap.put(projectionAnnotation, toPosition(foldingRegion));
}
private Position toPosition(FoldedPosition foldingRegion) {
return new Position(foldingRegion.offset, foldingRegion.length);
}
protected ProjectionAnnotation createProjectionAnnotation(
boolean allowCollapse, FoldedPosition foldingRegion) {
return new ProjectionAnnotation(allowCollapse);
}
/**
* Internal projection listener.
*/
public class ProjectionChangeListener implements IProjectionListener {
private ProjectionViewer projectionViewer;
/**
* Registers the listener with the viewer.
*
* @param viewer
* the viewer to register a listener with
*/
public ProjectionChangeListener(ProjectionViewer viewer) {
Assert.isLegal(viewer != null);
projectionViewer = viewer;
projectionViewer.addProjectionListener(this);
}
/**
* Disposes of this listener and removes the projection listener from
* the viewer.
*/
public void dispose() {
if (projectionViewer != null) {
projectionViewer.removeProjectionListener(this);
projectionViewer = null;
}
}
public void projectionEnabled() {
handleProjectionEnabled();
}
public void projectionDisabled() {
handleProjectionDisabled();
}
}
public void install(XtextEditor editor, ProjectionViewer viewer) {
}
}