/*
* $Id$
*
* Copyright (c) 2004-2010 by the TeXlapse Team.
* 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
*/
package net.sourceforge.texlipse.editor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sourceforge.texlipse.spelling.TexSpellingEngine.TexSpellingProblem;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector;
import org.eclipse.ui.texteditor.spelling.SpellingAnnotation;
import org.eclipse.ui.texteditor.spelling.SpellingContext;
import org.eclipse.ui.texteditor.spelling.SpellingProblem;
import org.eclipse.ui.texteditor.spelling.SpellingService;
/**
* Reconcile strategy used for spell checking TeX files. Most parts copied
* from {@link org.eclipse.ui.texteditor.spelling.SpellingReconcileStrategy}.
* We could not extend it, because of a bug in reconcile.
*/
public class TeXSpellingReconcileStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension {
/**
* Spelling problem collector. Copied from SpellingReconcileStrategy and changed
* it a little bit to ignore errors in TeX Commands.
*/
public static class TeXSpellingProblemCollector implements ISpellingProblemCollector {
/** Annotation model. */
private IAnnotationModel fAnnotationModel;
/** Annotations to add. */
private Map<Annotation, Position> fAddAnnotations;
/** Lock object for modifying the annotations. */
private Object fLockObject;
private IRegion[] regions;
/**
* Initializes this collector with the given annotation model.
*
* @param annotationModel the annotation model
*/
public TeXSpellingProblemCollector(IAnnotationModel annotationModel) {
Assert.isLegal(annotationModel != null);
fAnnotationModel= annotationModel;
if (fAnnotationModel instanceof ISynchronizable)
fLockObject= ((ISynchronizable)fAnnotationModel).getLockObject();
else
fLockObject= fAnnotationModel;
}
/*
* @see org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector#accept(org.eclipse.ui.texteditor.spelling.SpellingProblem)
*/
public void accept(SpellingProblem problem) {
fAddAnnotations.put(new SpellingAnnotation(problem), new Position(problem.getOffset(), problem.getLength()));
}
public void setRegions (IRegion[] _region) {
regions = _region.clone();
}
/*
* @see org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector#beginCollecting()
*/
public void beginCollecting() {
fAddAnnotations= new HashMap<Annotation, Position>();
}
/**
* @param p
* @return true, if the Position is inside a region which was checked by
* the spell checker
*/
private boolean wasChecked (Position p) {
for (IRegion r : regions) {
if (p.getOffset() >= r.getOffset() &&
p.getOffset() <= r.getOffset() + r.getLength()) {
return true;
}
}
return false;
}
/*
* @see org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector#endCollecting()
*/
public void endCollecting() {
List<Annotation> toRemove= new ArrayList<Annotation>();
synchronized (fLockObject) {
Iterator<Annotation> iter= fAnnotationModel.getAnnotationIterator();
while (iter.hasNext()) {
Annotation annotation= (Annotation)iter.next();
if (SpellingAnnotation.TYPE.equals(annotation.getType())) {
final Position p = fAnnotationModel.getPosition(annotation);
if (wasChecked(p)) toRemove.add(annotation);
else {
//Update position (Bug 2983142)
SpellingAnnotation spAnn = (SpellingAnnotation) annotation;
TexSpellingProblem problem = (TexSpellingProblem) spAnn.getSpellingProblem();
problem.setOffset(p.getOffset());
}
}
}
Annotation[] annotationsToRemove= (Annotation[])toRemove.toArray(new Annotation[toRemove.size()]);
if (fAnnotationModel instanceof IAnnotationModelExtension)
((IAnnotationModelExtension)fAnnotationModel).replaceAnnotations(annotationsToRemove, fAddAnnotations);
else {
for (int i= 0; i < annotationsToRemove.length; i++)
fAnnotationModel.removeAnnotation(annotationsToRemove[i]);
for (iter= fAddAnnotations.keySet().iterator(); iter.hasNext();) {
Annotation annotation= (Annotation)iter.next();
fAnnotationModel.addAnnotation(annotation, (Position)fAddAnnotations.get(annotation));
}
}
}
fAddAnnotations= null;
}
}
/** Text content type */
private static final IContentType TEXT_CONTENT_TYPE= Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);
/** The text editor to operate on. */
private ISourceViewer fViewer;
/** The document to operate on. */
private IDocument fDocument;
/** The progress monitor. */
private IProgressMonitor fProgressMonitor;
private SpellingService fSpellingService;
private TeXSpellingProblemCollector fSpellingProblemCollector;
/** The spelling context containing the Java source content type. */
private SpellingContext fSpellingContext;
/**
* Region array, used to prevent us from creating a new array on each reconcile pass.
* @since 3.4
*/
private IRegion[] fRegions= new IRegion[1];
/**
* Creates a new comment reconcile strategy.
*
* @param viewer the source viewer
* @param spellingService the spelling service to use
*/
public TeXSpellingReconcileStrategy(ISourceViewer viewer, SpellingService spellingService) {
Assert.isNotNull(viewer);
Assert.isNotNull(spellingService);
fViewer= viewer;
fSpellingService= spellingService;
fSpellingContext= new SpellingContext();
fSpellingContext.setContentType(getContentType());
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#initialReconcile()
*/
public void initialReconcile() {
reconcile(new Region(0, fDocument.getLength()));
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.reconciler.DirtyRegion,org.eclipse.jface.text.IRegion)
*/
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
try {
IRegion startLineInfo= fDocument.getLineInformationOfOffset(subRegion.getOffset());
IRegion endLineInfo= fDocument.getLineInformationOfOffset(subRegion.getOffset() + Math.max(0, subRegion.getLength() - 1));
if (startLineInfo.getOffset() == endLineInfo.getOffset())
subRegion= startLineInfo;
else
subRegion= new Region(startLineInfo.getOffset(), endLineInfo.getOffset() + endLineInfo.getLength() - startLineInfo.getOffset());
//Check everything from startLine to the end of the document, otherwise
//The positions of the errors are not in sync
//subRegion= new Region(startLineInfo.getOffset(), fDocument.getLength() - startLineInfo.getOffset());
} catch (BadLocationException e) {
subRegion= new Region(0, fDocument.getLength());
}
reconcile(subRegion);
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.IRegion)
*/
public void reconcile(IRegion region) {
if (getAnnotationModel() == null || fSpellingProblemCollector == null)
return;
fRegions[0]= region;
fSpellingProblemCollector.setRegions(fRegions);
fSpellingService.check(fDocument, fRegions, fSpellingContext, fSpellingProblemCollector, fProgressMonitor);
}
/**
* Returns the content type of the underlying editor input.
*
* @return the content type of the underlying editor input or
* <code>null</code> if none could be determined
*/
protected IContentType getContentType() {
return TEXT_CONTENT_TYPE;
}
/**
* Returns the document which is spell checked.
*
* @return the document
*/
protected final IDocument getDocument() {
return fDocument;
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#setDocument(org.eclipse.jface.text.IDocument)
*/
public void setDocument(IDocument document) {
fDocument= document;
fSpellingProblemCollector= createSpellingProblemCollector();
}
/**
* Creates a new spelling problem collector.
*
* @return the collector or <code>null</code> if none is available
*/
protected TeXSpellingProblemCollector createSpellingProblemCollector() {
IAnnotationModel model= getAnnotationModel();
if (model == null)
return null;
return new TeXSpellingProblemCollector(model);
}
/*
* @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#setProgressMonitor(org.eclipse.core.runtime.IProgressMonitor)
*/
public final void setProgressMonitor(IProgressMonitor monitor) {
fProgressMonitor= monitor;
}
/**
* Returns the annotation model to be used by this reconcile strategy.
*
* @return the annotation model of the underlying editor input or
* <code>null</code> if none could be determined
*/
protected IAnnotationModel getAnnotationModel() {
return fViewer.getAnnotationModel();
}
}