Changes:
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / PHPDocumentProvider.java
index 2fa0115..891f7f2 100644 (file)
@@ -12,18 +12,38 @@ Contributors:
     Klaus Hartlage - www.eclipseproject.de
 **********************************************************************/
 
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import net.sourceforge.phpdt.core.IProblemRequestor;
+import net.sourceforge.phpdt.core.compiler.IProblem;
+import net.sourceforge.phpdt.internal.ui.text.java.IProblemRequestorExtension;
 import net.sourceforge.phpeclipse.phpeditor.php.IPHPPartitionScannerConstants;
 import net.sourceforge.phpeclipse.phpeditor.php.PHPPartitionScanner;
 
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IMarkerDelta;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jface.text.DefaultLineTracker;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.jface.text.IDocumentPartitioner;
 import org.eclipse.jface.text.ILineTracker;
+import org.eclipse.jface.text.Position;
 import org.eclipse.jface.text.rules.DefaultPartitioner;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.AnnotationModelEvent;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelListener;
+import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
+import org.eclipse.jface.util.ListenerList;
+import org.eclipse.ui.IFileEditorInput;
 import org.eclipse.ui.editors.text.FileDocumentProvider;
 import org.eclipse.ui.part.FileEditorInput;
+import org.eclipse.ui.texteditor.MarkerAnnotation;
+import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;
 
 /** 
  * The PHPDocumentProvider provides the IDocuments used by java editors.
@@ -39,12 +59,421 @@ public class PHPDocumentProvider extends FileDocumentProvider {
       IPHPPartitionScannerConstants.HTML,
       IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT,
       IPHPPartitionScannerConstants.JAVASCRIPT,
-      IPHPPartitionScannerConstants.CSS };
+      IPHPPartitionScannerConstants.CSS,
+                       IPHPPartitionScannerConstants.SMARTY,
+                       IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT };
+
+  private static PHPPartitionScanner PHP_PARTITION_SCANNER = null;
+  private static PHPPartitionScanner HTML_PARTITION_SCANNER = null;
+  private static PHPPartitionScanner XML_PARTITION_SCANNER = null;
+  private static PHPPartitionScanner SMARTY_PARTITION_SCANNER = null;
+
+  /** annotation model listener added to all created CU annotation models */
+  //           private GlobalAnnotationModelListener fGlobalAnnotationModelListener;   
+
+  /**
+                * Internal structure for mapping positions to some value. 
+                * The reason for this specific structure is that positions can
+                * change over time. Thus a lookup is based on value and not
+                * on hash value.
+                */
+  protected static class ReverseMap {
+
+    static class Entry {
+      Position fPosition;
+      Object fValue;
+    };
+
+    private List fList = new ArrayList(2);
+    private int fAnchor = 0;
+
+    public ReverseMap() {
+    }
+
+    public Object get(Position position) {
+
+      Entry entry;
+
+      // behind anchor
+      int length = fList.size();
+      for (int i = fAnchor; i < length; i++) {
+        entry = (Entry) fList.get(i);
+        if (entry.fPosition.equals(position)) {
+          fAnchor = i;
+          return entry.fValue;
+        }
+      }
+
+      // before anchor
+      for (int i = 0; i < fAnchor; i++) {
+        entry = (Entry) fList.get(i);
+        if (entry.fPosition.equals(position)) {
+          fAnchor = i;
+          return entry.fValue;
+        }
+      }
 
-  private static PHPPartitionScanner fgScanner = null;
+      return null;
+    }
+
+    private int getIndex(Position position) {
+      Entry entry;
+      int length = fList.size();
+      for (int i = 0; i < length; i++) {
+        entry = (Entry) fList.get(i);
+        if (entry.fPosition.equals(position))
+          return i;
+      }
+      return -1;
+    }
+
+    public void put(Position position, Object value) {
+      int index = getIndex(position);
+      if (index == -1) {
+        Entry entry = new Entry();
+        entry.fPosition = position;
+        entry.fValue = value;
+        fList.add(entry);
+      } else {
+        Entry entry = (Entry) fList.get(index);
+        entry.fValue = value;
+      }
+    }
+
+    public void remove(Position position) {
+      int index = getIndex(position);
+      if (index > -1)
+        fList.remove(index);
+    }
+
+    public void clear() {
+      fList.clear();
+    }
+  };
+
+  /**
+                * Annotation model dealing with java marker annotations and temporary problems.
+                * Also acts as problem requestor for its compilation unit. Initialiy inactive. Must explicitly be
+                * activated.
+                */
+  protected class CompilationUnitAnnotationModel
+    extends ResourceMarkerAnnotationModel
+    implements IProblemRequestor, IProblemRequestorExtension {
+
+    private IFileEditorInput fInput;
+    private List fCollectedProblems;
+    private List fGeneratedAnnotations;
+    private IProgressMonitor fProgressMonitor;
+    private boolean fIsActive = false;
+
+    private ReverseMap fReverseMap = new ReverseMap();
+    private List fPreviouslyOverlaid = null;
+    private List fCurrentlyOverlaid = new ArrayList();
+
+    public CompilationUnitAnnotationModel(IFileEditorInput input) {
+      super(input.getFile());
+      fInput = input;
+    }
+
+    protected MarkerAnnotation createMarkerAnnotation(IMarker marker) {
+      return new JavaMarkerAnnotation(marker);
+    }
+
+    protected Position createPositionFromProblem(IProblem problem) {
+      int start = problem.getSourceStart();
+      if (start < 0)
+        return null;
+
+      int length = problem.getSourceEnd() - problem.getSourceStart() + 1;
+      if (length < 0)
+        return null;
+
+      return new Position(start, length);
+    }
+
+    protected void update(IMarkerDelta[] markerDeltas) {
+
+      super.update(markerDeltas);
+
+      //                                       if (markerDeltas != null && markerDeltas.length > 0) {
+      //                                               try {
+      //                                                       ICompilationUnit workingCopy = getWorkingCopy(fInput);
+      //                                                       if (workingCopy != null)
+      //                                                               workingCopy.reconcile(true, null);
+      //                                               } catch (JavaModelException ex) {
+      //                                                       handleCoreException(ex, ex.getMessage());
+      //                                               }
+      //                                       }
+    }
+
+    /*
+     * @see IProblemRequestor#beginReporting()
+     */
+    public void beginReporting() {
+      //                                       ICompilationUnit unit= getWorkingCopy(fInput);
+      //                                       if (unit != null && unit.getJavaProject().isOnClasspath(unit))
+      //                                               fCollectedProblems= new ArrayList();
+      //                                       else
+      fCollectedProblems = null;
+    }
+
+    /*
+     * @see IProblemRequestor#acceptProblem(IProblem)
+     */
+    public void acceptProblem(IProblem problem) {
+      if (isActive())
+        fCollectedProblems.add(problem);
+    }
+
+    /*
+     * @see IProblemRequestor#endReporting()
+     */
+    public void endReporting() {
+      if (!isActive())
+        return;
+
+      if (fProgressMonitor != null && fProgressMonitor.isCanceled())
+        return;
+
+      boolean isCanceled = false;
+      boolean temporaryProblemsChanged = false;
+      fPreviouslyOverlaid = fCurrentlyOverlaid;
+      fCurrentlyOverlaid = new ArrayList();
+
+      synchronized (fAnnotations) {
+
+        if (fGeneratedAnnotations.size() > 0) {
+          temporaryProblemsChanged = true;
+          removeAnnotations(fGeneratedAnnotations, false, true);
+          fGeneratedAnnotations.clear();
+        }
+
+        if (fCollectedProblems != null && fCollectedProblems.size() > 0) {
+
+          Iterator e = fCollectedProblems.iterator();
+          while (e.hasNext()) {
+
+            IProblem problem = (IProblem) e.next();
+
+            if (fProgressMonitor != null && fProgressMonitor.isCanceled()) {
+              isCanceled = true;
+              break;
+            }
+
+            Position position = createPositionFromProblem(problem);
+            if (position != null) {
+
+              //                                                                       ProblemAnnotation annotation= new ProblemAnnotation(problem);
+              //                                                                       overlayMarkers(position, annotation);                                                           
+              //                                                                       fGeneratedAnnotations.add(annotation);
+              //                                                                       addAnnotation(annotation, position, false);
+
+              temporaryProblemsChanged = true;
+            }
+          }
+
+          fCollectedProblems.clear();
+        }
+
+        removeMarkerOverlays(isCanceled);
+        fPreviouslyOverlaid.clear();
+        fPreviouslyOverlaid = null;
+      }
+
+      //                                       if (temporaryProblemsChanged)
+      //                                               fireModelChanged(new CompilationUnitAnnotationModelEvent(this, getResource(), false));
+    }
+
+    private void removeMarkerOverlays(boolean isCanceled) {
+      if (isCanceled) {
+        fCurrentlyOverlaid.addAll(fPreviouslyOverlaid);
+      } else if (fPreviouslyOverlaid != null) {
+        Iterator e = fPreviouslyOverlaid.iterator();
+        while (e.hasNext()) {
+          JavaMarkerAnnotation annotation = (JavaMarkerAnnotation) e.next();
+          annotation.setOverlay(null);
+        }
+      }
+    }
+
+    /**
+     * Overlays value with problem annotation.
+     * @param problemAnnotation
+     */
+    //                         private void setOverlay(Object value, ProblemAnnotation problemAnnotation) {
+    //                                 if (value instanceof  JavaMarkerAnnotation) {
+    //                                         JavaMarkerAnnotation annotation= (JavaMarkerAnnotation) value;
+    //                                         if (annotation.isProblem()) {
+    //                                                 annotation.setOverlay(problemAnnotation);
+    //                                                 fPreviouslyOverlaid.remove(annotation);
+    //                                                 fCurrentlyOverlaid.add(annotation);
+    //                                         }
+    //                                 }
+    //                         }
+
+    //                         private void  overlayMarkers(Position position, ProblemAnnotation problemAnnotation) {
+    //                                 Object value= getAnnotations(position);
+    //                                 if (value instanceof List) {
+    //                                         List list= (List) value;
+    //                                         for (Iterator e = list.iterator(); e.hasNext();)
+    //                                                 setOverlay(e.next(), problemAnnotation);
+    //                                 } else {
+    //                                         setOverlay(value, problemAnnotation);
+    //                                 }
+    //                         }
+
+    /**
+     * Tells this annotation model to collect temporary problems from now on.
+     */
+    private void startCollectingProblems() {
+      fCollectedProblems = new ArrayList();
+      fGeneratedAnnotations = new ArrayList();
+    }
+
+    /**
+     * Tells this annotation model to no longer collect temporary problems.
+     */
+    private void stopCollectingProblems() {
+      if (fGeneratedAnnotations != null) {
+        removeAnnotations(fGeneratedAnnotations, true, true);
+        fGeneratedAnnotations.clear();
+      }
+      fCollectedProblems = null;
+      fGeneratedAnnotations = null;
+    }
+
+    /*
+     * @see AnnotationModel#fireModelChanged()
+     */
+    //                         protected void fireModelChanged() {
+    //                                 fireModelChanged(new CompilationUnitAnnotationModelEvent(this, getResource(), true));
+    //                         }
+
+    /*
+     * @see IProblemRequestor#isActive()
+     */
+    public boolean isActive() {
+      return fIsActive && (fCollectedProblems != null);
+    }
+
+    /*
+     * @see IProblemRequestorExtension#setProgressMonitor(IProgressMonitor)
+     */
+    public void setProgressMonitor(IProgressMonitor monitor) {
+      fProgressMonitor = monitor;
+    }
+
+    /*
+     * @see IProblemRequestorExtension#setIsActive(boolean)
+     */
+    public void setIsActive(boolean isActive) {
+      if (fIsActive != isActive) {
+        fIsActive = isActive;
+        if (fIsActive)
+          startCollectingProblems();
+        else
+          stopCollectingProblems();
+      }
+    }
+
+    private Object getAnnotations(Position position) {
+      return fReverseMap.get(position);
+    }
+
+    /*
+     * @see AnnotationModel#addAnnotation(Annotation, Position, boolean)
+     */
+    protected void addAnnotation(Annotation annotation, Position position, boolean fireModelChanged) {
+      super.addAnnotation(annotation, position, fireModelChanged);
+
+      Object cached = fReverseMap.get(position);
+      if (cached == null)
+        fReverseMap.put(position, annotation);
+      else if (cached instanceof List) {
+        List list = (List) cached;
+        list.add(annotation);
+      } else if (cached instanceof Annotation) {
+        List list = new ArrayList(2);
+        list.add(cached);
+        list.add(annotation);
+        fReverseMap.put(position, list);
+      }
+    }
+
+    /*
+     * @see AnnotationModel#removeAllAnnotations(boolean)
+     */
+    protected void removeAllAnnotations(boolean fireModelChanged) {
+      super.removeAllAnnotations(fireModelChanged);
+      fReverseMap.clear();
+    }
+
+    /*
+     * @see AnnotationModel#removeAnnotation(Annotation, boolean)
+     */
+    protected void removeAnnotation(Annotation annotation, boolean fireModelChanged) {
+      Position position = getPosition(annotation);
+      Object cached = fReverseMap.get(position);
+      if (cached instanceof List) {
+        List list = (List) cached;
+        list.remove(annotation);
+        if (list.size() == 1) {
+          fReverseMap.put(position, list.get(0));
+          list.clear();
+        }
+      } else if (cached instanceof Annotation) {
+        fReverseMap.remove(position);
+      }
+
+      super.removeAnnotation(annotation, fireModelChanged);
+    }
+  };
+
+  protected static class GlobalAnnotationModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension {
+
+    private ListenerList fListenerList;
+
+    public GlobalAnnotationModelListener() {
+      fListenerList = new ListenerList();
+    }
+
+    /**
+     * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
+     */
+    public void modelChanged(IAnnotationModel model) {
+      Object[] listeners = fListenerList.getListeners();
+      for (int i = 0; i < listeners.length; i++) {
+        ((IAnnotationModelListener) listeners[i]).modelChanged(model);
+      }
+    }
+
+    /**
+     * @see IAnnotationModelListenerExtension#modelChanged(AnnotationModelEvent)
+     */
+    public void modelChanged(AnnotationModelEvent event) {
+      Object[] listeners = fListenerList.getListeners();
+      for (int i = 0; i < listeners.length; i++) {
+        Object curr = listeners[i];
+        if (curr instanceof IAnnotationModelListenerExtension) {
+          ((IAnnotationModelListenerExtension) curr).modelChanged(event);
+        }
+      }
+    }
+
+    public void addListener(IAnnotationModelListener listener) {
+      fListenerList.add(listener);
+    }
+
+    public void removeListener(IAnnotationModelListener listener) {
+      fListenerList.remove(listener);
+    }
+  };
 
   public PHPDocumentProvider() {
     super();
+
+    //         fGlobalAnnotationModelListener= new GlobalAnnotationModelListener();
+
   }
 
   /* (non-Javadoc)
@@ -59,7 +488,7 @@ public class PHPDocumentProvider extends FileDocumentProvider {
         IFile file = (IFile) ((FileEditorInput) element).getAdapter(IFile.class);
         String filename = file.getLocation().toString();
         String extension = filename.substring(filename.lastIndexOf("."), filename.length());
-     //   System.out.println(extension);
+        //   System.out.println(extension);
         if (extension.equalsIgnoreCase(".html") || extension.equalsIgnoreCase(".htm")) {
           // html
           partitioner = createHTMLPartitioner();
@@ -101,23 +530,23 @@ public class PHPDocumentProvider extends FileDocumentProvider {
    * Return a partitioner for .html files.
    */
   private IDocumentPartitioner createHTMLPartitioner() {
-    return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
+    return new DefaultPartitioner(getHTMLPartitionScanner(), TYPES);
   }
 
   private IDocumentPartitioner createXMLPartitioner() {
-    return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
+    return new DefaultPartitioner(getXMLPartitionScanner(), TYPES);
   }
 
   private IDocumentPartitioner createJavaScriptPartitioner() {
-    return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
+    return new DefaultPartitioner(getHTMLPartitionScanner(), TYPES);
   }
 
   private IDocumentPartitioner createCSSPartitioner() {
-    return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
+    return new DefaultPartitioner(getHTMLPartitionScanner(), TYPES);
   }
 
   private IDocumentPartitioner createSmartyPartitioner() {
-    return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
+    return new DefaultPartitioner(getSmartyPartitionScanner(), TYPES);
   }
 
   private IDocumentPartitioner createIncludePartitioner() {
@@ -127,9 +556,36 @@ public class PHPDocumentProvider extends FileDocumentProvider {
    * Return a scanner for creating php partitions.
    */
   private PHPPartitionScanner getPHPPartitionScanner() {
-    if (fgScanner == null)
-      fgScanner = new PHPPartitionScanner();
-    return fgScanner;
+    if (PHP_PARTITION_SCANNER == null)
+      PHP_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.PHP_FILE);
+    return PHP_PARTITION_SCANNER;
+  }
+
+  /**
+   * Return a scanner for creating html partitions.
+   */
+  private PHPPartitionScanner getHTMLPartitionScanner() {
+    if (HTML_PARTITION_SCANNER == null)
+      HTML_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.HTML_FILE);
+    return HTML_PARTITION_SCANNER;
+  }
+
+  /**
+   * Return a scanner for creating xml partitions.
+   */
+  private PHPPartitionScanner getXMLPartitionScanner() {
+    if (XML_PARTITION_SCANNER == null)
+      XML_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.XML_FILE);
+    return XML_PARTITION_SCANNER;
+  }
+
+  /**
+   * Return a scanner for creating smarty partitions.
+   */
+  private PHPPartitionScanner getSmartyPartitionScanner() {
+    if (SMARTY_PARTITION_SCANNER == null)
+      SMARTY_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.SMARTY_FILE);
+    return SMARTY_PARTITION_SCANNER;
   }
 
   /**