Package org.eclipse.jface.text.source.projection

Source Code of org.eclipse.jface.text.source.projection.ProjectionViewer$ProjectionCommand

/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation 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:
*     IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.text.source.projection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.SWTError;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentInformationMappingExtension;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISlaveDocumentManager;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.projection.ProjectionDocument;
import org.eclipse.jface.text.projection.ProjectionDocumentEvent;
import org.eclipse.jface.text.projection.ProjectionDocumentManager;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModelEvent;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.IAnnotationModelListener;
import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerColumn;
import org.eclipse.jface.text.source.SourceViewer;


/**
* A projection source viewer is a source viewer which supports multiple visible
* regions which can dynamically be changed.
* <p>
* A projection source viewer uses a <code>ProjectionDocumentManager</code>
* for the management of the visible document.</p>
* <p>
* NOTE: The <code>ProjectionViewer</code> only supports projections that cover full lines.
* </p>
* <p>
* This class should not be subclassed.</p>
*
* @since 3.0
* @noextend This class is not intended to be subclassed by clients.
*/
public class ProjectionViewer extends SourceViewer implements ITextViewerExtension5 {

  private static final int BASE= INFORMATION; // see ISourceViewer.INFORMATION

  /** Operation constant for the expand operation. */
  public static final int EXPAND= BASE + 1;
  /** Operation constant for the collapse operation. */
  public static final int COLLAPSE= BASE + 2;
  /** Operation constant for the toggle projection operation. */
  public static final int TOGGLE= BASE + 3;
  /** Operation constant for the expand all operation. */
  public static final int EXPAND_ALL= BASE + 4;
  /**
   * Operation constant for the collapse all operation.
   *
   * @since 3.2
   */
  public static final int COLLAPSE_ALL= BASE + 5;

  /**
   * Internal listener to changes of the annotation model.
   */
  private class AnnotationModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension {

    /*
     * @see org.eclipse.jface.text.source.IAnnotationModelListener#modelChanged(org.eclipse.jface.text.source.IAnnotationModel)
     */
    public void modelChanged(IAnnotationModel model) {
      processModelChanged(model, null);
    }

    /*
     * @see org.eclipse.jface.text.source.IAnnotationModelListenerExtension#modelChanged(org.eclipse.jface.text.source.AnnotationModelEvent)
     */
    public void modelChanged(AnnotationModelEvent event) {
      processModelChanged(event.getAnnotationModel(), event);
    }

    private void processModelChanged(IAnnotationModel model, AnnotationModelEvent event) {
      if (model == fProjectionAnnotationModel) {

        if (fProjectionSummary != null)
          fProjectionSummary.updateSummaries();
        processCatchupRequest(event);

      } else if (model == getAnnotationModel() && fProjectionSummary != null)
        fProjectionSummary.updateSummaries();
    }
  }

  /**
   * Executes the 'replaceVisibleDocument' operation when called the first time. Self-destructs afterwards.
   */
  private class ReplaceVisibleDocumentExecutor implements IDocumentListener {

    private IDocument fSlaveDocument;
    private IDocument fExecutionTrigger;

    /**
     * Creates a new executor in order to free the given slave document.
     *
     * @param slaveDocument the slave document to free
     */
    public ReplaceVisibleDocumentExecutor(IDocument slaveDocument) {
      fSlaveDocument= slaveDocument;
    }

    /**
     * Installs this executor on the given trigger document.
     *
     * @param executionTrigger the trigger document
     */
    public void install(IDocument executionTrigger) {
      if (executionTrigger != null && fSlaveDocument != null) {
        fExecutionTrigger= executionTrigger;
        fExecutionTrigger.addDocumentListener(this);
      }
    }

    /*
     * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
     */
    public void documentAboutToBeChanged(DocumentEvent event) {
    }

    /*
     * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
     */
    public void documentChanged(DocumentEvent event) {
      fExecutionTrigger.removeDocumentListener(this);
      executeReplaceVisibleDocument(fSlaveDocument);
    }
  }

  /**
   * A command representing a change of the projection document. This can be either
   * adding a master document range, removing a master document change, or invalidating
   * the viewer text presentation.
   */
  private static class ProjectionCommand {

    final static int ADD= 0;
    final static int REMOVE= 1;
    final static int INVALIDATE_PRESENTATION= 2;

    ProjectionDocument fProjection;
    int fType;
    int fOffset;
    int fLength;

    ProjectionCommand(ProjectionDocument projection, int type, int offset, int length) {
      fProjection= projection;
      fType= type;
      fOffset= offset;
      fLength= length;
    }

    ProjectionCommand(int offset, int length) {
      fType= INVALIDATE_PRESENTATION;
      fOffset= offset;
      fLength= length;
    }

    int computeExpectedCosts() {

      switch(fType) {
        case ADD: {
          try {
            IRegion[] gaps= fProjection.computeUnprojectedMasterRegions(fOffset, fLength);
            return gaps == null ? 0 : gaps.length;
          } catch (BadLocationException x) {
          }
          break;
        }
        case REMOVE: {
          try {
            IRegion[] fragments= fProjection.computeProjectedMasterRegions(fOffset, fLength);
            return fragments == null ? 0 : fragments.length;
          } catch (BadLocationException x) {
          }
          break;
        }
      }
      return 0;
    }
  }

  /**
   * The queue of projection command objects.
   */
  private static class ProjectionCommandQueue {

    final static int REDRAW_COSTS= 15;
    final static int INVALIDATION_COSTS= 10;

    List fList= new ArrayList(15);
    int fExpectedExecutionCosts= -1;


    void add(ProjectionCommand command) {
      fList.add(command);
    }

    Iterator iterator() {
      return fList.iterator();
    }

    void clear() {
      fList.clear();
      fExpectedExecutionCosts= -1;
    }

    boolean passedRedrawCostsThreshold() {
      if (fExpectedExecutionCosts == -1)
        computeExpectedExecutionCosts();
      return fExpectedExecutionCosts > REDRAW_COSTS;
    }

    boolean passedInvalidationCostsThreshold() {
      if (fExpectedExecutionCosts == -1)
        computeExpectedExecutionCosts();
      return fExpectedExecutionCosts > INVALIDATION_COSTS;
    }

    private void computeExpectedExecutionCosts() {
      int max_costs= Math.max(REDRAW_COSTS, INVALIDATION_COSTS);
      fExpectedExecutionCosts= fList.size();
      if (fExpectedExecutionCosts <= max_costs) {
        ProjectionCommand command;
        Iterator e= fList.iterator();
        while (e.hasNext()) {
          command= (ProjectionCommand) e.next();
          fExpectedExecutionCosts += command.computeExpectedCosts();
          if (fExpectedExecutionCosts > max_costs)
            break;
        }
      }
    }
  }

  /** The projection annotation model used by this viewer. */
  private ProjectionAnnotationModel fProjectionAnnotationModel;
  /** The annotation model listener */
  private IAnnotationModelListener fAnnotationModelListener= new AnnotationModelListener();
  /** The projection summary. */
  private ProjectionSummary fProjectionSummary;
  /** Indication that an annotation world change has not yet been processed. */
  private boolean fPendingAnnotationWorldChange= false;
  /** Indication whether projection changes in the visible document should be considered. */
  private boolean fHandleProjectionChanges= true;
  /** The list of projection listeners. */
  private List fProjectionListeners;
  /** Internal lock for protecting the list of pending requests */
  private Object fLock= new Object();
  /** The list of pending requests */
  private List fPendingRequests= new ArrayList();
  /** The replace-visible-document execution trigger */
  private IDocument fReplaceVisibleDocumentExecutionTrigger;
  /** <code>true</code> if projection was on the last time we switched to segmented mode. */
  private boolean fWasProjectionEnabled;
  /** The queue of projection commands used to assess the costs of projection changes. */
  private ProjectionCommandQueue fCommandQueue;
  /**
   * The amount of lines deleted by the last document event issued by the
   * visible document event.
   * @since 3.1
   */
  private int fDeletedLines;


  /**
   * Creates a new projection source viewer.
   *
   * @param parent the SWT parent control
   * @param ruler the vertical ruler
   * @param overviewRuler the overview ruler
   * @param showsAnnotationOverview <code>true</code> if the overview ruler should be shown
   * @param styles the SWT style bits
   */
  public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) {
    super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
  }

  /**
   * Sets the projection summary for this viewer.
   *
   * @param projectionSummary the projection summary.
   */
  public void setProjectionSummary(ProjectionSummary projectionSummary) {
    fProjectionSummary= projectionSummary;
  }

  /**
   * Adds the projection annotation model to the given annotation model.
   *
   * @param model the model to which the projection annotation model is added
   */
  private void addProjectionAnnotationModel(IAnnotationModel model) {
    if (model instanceof IAnnotationModelExtension) {
      IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
      extension.addAnnotationModel(ProjectionSupport.PROJECTION, fProjectionAnnotationModel);
      model.addAnnotationModelListener(fAnnotationModelListener);
    }
  }

  /**
   * Removes the projection annotation model from the given annotation model.
   *
   * @param model the mode from which the projection annotation model is removed
   * @return the removed projection annotation model or <code>null</code> if there was none
   */
  private IAnnotationModel removeProjectionAnnotationModel(IAnnotationModel model) {
    if (model instanceof IAnnotationModelExtension) {
      model.removeAnnotationModelListener(fAnnotationModelListener);
      IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
      return extension.removeAnnotationModel(ProjectionSupport.PROJECTION);
    }
    return null;
  }

  /*
   * @see org.eclipse.jface.text.source.SourceViewer#setDocument(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.source.IAnnotationModel, int, int)
   */
  public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) {
    boolean wasProjectionEnabled= false;

    synchronized (fLock) {
      fPendingRequests.clear();
    }

    if (fProjectionAnnotationModel != null) {
      wasProjectionEnabled= removeProjectionAnnotationModel(getVisualAnnotationModel()) != null;
      fProjectionAnnotationModel= null;
    }

    super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength);

    if (wasProjectionEnabled && document != null)
      enableProjection();
  }

  /*
   * @see org.eclipse.jface.text.source.SourceViewer#createVisualAnnotationModel(org.eclipse.jface.text.source.IAnnotationModel)
   */
  protected IAnnotationModel createVisualAnnotationModel(IAnnotationModel annotationModel) {
    IAnnotationModel model= super.createVisualAnnotationModel(annotationModel);
    fProjectionAnnotationModel= new ProjectionAnnotationModel();
    return model;
  }

  /**
   * Returns the projection annotation model.
   *
   * @return the projection annotation model
   */
  public ProjectionAnnotationModel getProjectionAnnotationModel() {
    IAnnotationModel model= getVisualAnnotationModel();
    if (model instanceof IAnnotationModelExtension) {
      IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
      return (ProjectionAnnotationModel) extension.getAnnotationModel(ProjectionSupport.PROJECTION);
    }
    return null;
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#createSlaveDocumentManager()
   */
  protected ISlaveDocumentManager createSlaveDocumentManager() {
    return new ProjectionDocumentManager();
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#updateSlaveDocument(org.eclipse.jface.text.IDocument, int, int)
   */
  protected boolean updateSlaveDocument(IDocument slaveDocument, int modelRangeOffset, int modelRangeLength) throws BadLocationException {
    if (slaveDocument instanceof ProjectionDocument) {
      ProjectionDocument projection= (ProjectionDocument) slaveDocument;

      int offset= modelRangeOffset;
      int length= modelRangeLength;

      if (!isProjectionMode()) {
        // mimic original TextViewer behavior
        IDocument master= projection.getMasterDocument();
        int line= master.getLineOfOffset(modelRangeOffset);
        offset= master.getLineOffset(line);
        length= (modelRangeOffset - offset) + modelRangeLength;

      }

      try {
        fHandleProjectionChanges= false;
        projection.replaceMasterDocumentRanges(offset, length);
      } finally {
        fHandleProjectionChanges= true;
      }
      return true;
    }
    return false;
  }

  /**
   * Adds a projection annotation listener to this viewer. The listener may
   * not be <code>null</code>. If the listener is already registered, this method
   * does not have any effect.
   *
   * @param listener the listener to add
   */
  public void addProjectionListener(IProjectionListener listener) {

    Assert.isNotNull(listener);

    if (fProjectionListeners == null)
      fProjectionListeners= new ArrayList();

    if (!fProjectionListeners.contains(listener))
      fProjectionListeners.add(listener);
  }

  /**
   * Removes the given listener from this viewer. The listener may not be
   * <code>null</code>. If the listener is not registered with this viewer,
   * this method is without effect.
   *
   * @param listener the listener to remove
   */
  public void removeProjectionListener(IProjectionListener listener) {

    Assert.isNotNull(listener);

    if (fProjectionListeners != null) {
      fProjectionListeners.remove(listener);
      if (fProjectionListeners.size() == 0)
        fProjectionListeners= null;
    }
  }

  /**
   * Notifies all registered projection listeners
   * that projection mode has been enabled.
   */
  protected void fireProjectionEnabled() {
    if (fProjectionListeners != null) {
      Iterator e= new ArrayList(fProjectionListeners).iterator();
      while (e.hasNext()) {
        IProjectionListener l= (IProjectionListener) e.next();
        l.projectionEnabled();
      }
    }
  }

  /**
   * Notifies all registered projection listeners
   * that projection mode has been disabled.
   */
  protected void fireProjectionDisabled() {
    if (fProjectionListeners != null) {
      Iterator e= new ArrayList(fProjectionListeners).iterator();
      while (e.hasNext()) {
        IProjectionListener l= (IProjectionListener) e.next();
        l.projectionDisabled();
      }
    }
  }

  /**
   * Returns whether this viewer is in projection mode.
   *
   * @return <code>true</code> if this viewer is in projection mode,
   *         <code>false</code> otherwise
   */
  public final boolean isProjectionMode() {
    return getProjectionAnnotationModel() != null;
  }

  /**
   * Disables the projection mode.
   */
  public final void disableProjection() {
    if (isProjectionMode()) {
      removeProjectionAnnotationModel(getVisualAnnotationModel());
      fProjectionAnnotationModel.removeAllAnnotations();
      fFindReplaceDocumentAdapter= null;
      fireProjectionDisabled();
    }
  }

  /**
   * Enables the projection mode.
   */
  public final void enableProjection() {
    if (!isProjectionMode()) {
      addProjectionAnnotationModel(getVisualAnnotationModel());
      fFindReplaceDocumentAdapter= null;
      fireProjectionEnabled();
    }
  }

  private void expandAll() {
    int offset= 0;
    IDocument doc= getDocument();
    int length= doc == null ? 0 : doc.getLength();
    if (isProjectionMode()) {
      fProjectionAnnotationModel.expandAll(offset, length);
    }
  }

  private void expand() {
    if (isProjectionMode()) {
      Position found= null;
      Annotation bestMatch= null;
      Point selection= getSelectedRange();
      for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
        ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
        if (annotation.isCollapsed()) {
          Position position= fProjectionAnnotationModel.getPosition(annotation);
          // take the first most fine grained match
          if (position != null && touches(selection, position))
            if (found == null || position.includes(found.offset) && position.includes(found.offset + found.length)) {
              found= position;
              bestMatch= annotation;
            }
        }
      }

      if (bestMatch != null) {
        fProjectionAnnotationModel.expand(bestMatch);
        revealRange(selection.x, selection.y);
      }
    }
  }

  private boolean touches(Point selection, Position position) {
    return position.overlapsWith(selection.x, selection.y) || selection.y == 0 && position.offset + position.length == selection.x + selection.y;
  }

  private void collapse() {
    if (isProjectionMode()) {
      Position found= null;
      Annotation bestMatch= null;
      Point selection= getSelectedRange();
      for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
        ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
        if (!annotation.isCollapsed()) {
          Position position= fProjectionAnnotationModel.getPosition(annotation);
          // take the first most fine grained match
          if (position != null && touches(selection, position))
            if (found == null || found.includes(position.offset) && found.includes(position.offset + position.length)) {
              found= position;
              bestMatch= annotation;
            }
        }
      }

      if (bestMatch != null) {
        fProjectionAnnotationModel.collapse(bestMatch);
        revealRange(selection.x, selection.y);
      }
    }
  }

  /*
   * @since 3.2
   */
  private void collapseAll() {
    int offset= 0;
    IDocument doc= getDocument();
    int length= doc == null ? 0 : doc.getLength();
    if (isProjectionMode()) {
      fProjectionAnnotationModel.collapseAll(offset, length);
    }
  }

  /**
   * Adds the given master range to the given projection document. While the
   * modification is processed, the viewer no longer handles projection
   * changes, as it is causing them.
   *
   * @param projection the projection document
   * @param offset the offset in the master document
   * @param length the length in the master document
   * @throws BadLocationException in case the specified range is invalid
   *
   * @see ProjectionDocument#addMasterDocumentRange(int, int)
   */
  private void addMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException {

    if (fCommandQueue != null) {
      fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.ADD, offset, length));
    } else {
      try {
        fHandleProjectionChanges= false;
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258
        // make sure the document range is strictly line based
        //XXX: Lambda4jdt Commented out bugfix.
        //It is enabler for Lambda4jdt. Besides, following bug108258 it is obvious that
        //this fix doesnt solve anything, but does strictly limit projection infrastructure
        // int end= offset + length;
        // offset= toLineStart(projection.getMasterDocument(), offset, false);
        // length= toLineStart(projection.getMasterDocument(), end, true) - offset;
        //XXX: end hack
        projection.addMasterDocumentRange(offset, length);
      } finally {
        fHandleProjectionChanges= true;
      }
    }
  }

  /**
   * Removes the given master range from the given projection document. While the
   * modification is processed, the viewer no longer handles projection
   * changes, as it is causing them.
   *
   * @param projection the projection document
   * @param offset the offset in the master document
   * @param length the length in the master document
   * @throws BadLocationException in case the specified range is invalid
   *
   * @see ProjectionDocument#removeMasterDocumentRange(int, int)
   */
  private void removeMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException {
    if (fCommandQueue != null) {
      fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.REMOVE, offset, length));
    } else {
      try {
        fHandleProjectionChanges= false;
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258
        // make sure the document range is strictly line based
        //XXX: Lambda4jdt Commented out bugfix.
        //It is enabler for Lambda4jdt. Besides, following bug108258 it is obvious that
        //this fix doesnt solve anything, but does strictly limit projection infrastructure
        // int end= offset + length;
        // offset= toLineStart(projection.getMasterDocument(), offset, false);
        // length= toLineStart(projection.getMasterDocument(), end, true) - offset;
        //XXX: end hack
        projection.removeMasterDocumentRange(offset, length);
      } finally {
        fHandleProjectionChanges= true;
      }
    }
  }
 
 
 
  /**
   * Returns the first line offset &lt;= <code>offset</code>. If <code>testLastLine</code>
   * is <code>true</code> and the offset is on last line then <code>offset</code> is returned.
   *
   * @param document the document
   * @param offset the master document offset
   * @param testLastLine <code>true</code> if the test for the last line should be performed
   * @return the closest line offset &gt;= <code>offset</code>
   * @throws BadLocationException if the offset is invalid
   * @since 3.2
   */
  private int toLineStart(IDocument document, int offset, boolean testLastLine) throws BadLocationException {
    if (document == null)
      return offset;
   
    if (testLastLine && offset >= document.getLineInformationOfOffset(document.getLength() - 1).getOffset())
      return offset;
   
    return document.getLineInformationOfOffset(offset).getOffset();
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#setVisibleRegion(int, int)
   */
  public void setVisibleRegion(int start, int length) {
    fWasProjectionEnabled= isProjectionMode();
    disableProjection();
    super.setVisibleRegion(start, length);
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#setVisibleDocument(org.eclipse.jface.text.IDocument)
   */
  protected void setVisibleDocument(IDocument document) {
    if (!isProjectionMode()) {
      super.setVisibleDocument(document);
      return;
    }

    // In projection mode we don't want to throw away the find/replace document adapter
    FindReplaceDocumentAdapter adapter= fFindReplaceDocumentAdapter;
    super.setVisibleDocument(document);
    fFindReplaceDocumentAdapter= adapter;
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#resetVisibleRegion()
   */
  public void resetVisibleRegion() {
    super.resetVisibleRegion();
    if (fWasProjectionEnabled)
      enableProjection();
  }

  /*
   * @see org.eclipse.jface.text.ITextViewer#getVisibleRegion()
   */
  public IRegion getVisibleRegion() {
    disableProjection();
    IRegion visibleRegion= getModelCoverage();
    if (visibleRegion == null)
      visibleRegion= new Region(0, 0);

    return visibleRegion;
  }

  /*
   * @see org.eclipse.jface.text.ITextViewer#overlapsWithVisibleRegion(int,int)
   */
  public boolean overlapsWithVisibleRegion(int offset, int length) {
    disableProjection();
    IRegion coverage= getModelCoverage();
    if (coverage == null)
      return false;

    boolean appending= (offset == coverage.getOffset() + coverage.getLength()) && length == 0;
    return appending || TextUtilities.overlaps(coverage, new Region(offset, length));
  }

  /**
   * Replace the visible document with the given document. Maintains the
   * scroll offset and the selection.
   *
   * @param slave the visible document
   */
  private void replaceVisibleDocument(IDocument slave) {
    if (fReplaceVisibleDocumentExecutionTrigger != null) {
      ReplaceVisibleDocumentExecutor executor= new ReplaceVisibleDocumentExecutor(slave);
      executor.install(fReplaceVisibleDocumentExecutionTrigger);
    } else
      executeReplaceVisibleDocument(slave);
  }


  private void executeReplaceVisibleDocument(IDocument visibleDocument) {
    StyledText textWidget= getTextWidget();
    try {
      if (textWidget != null && !textWidget.isDisposed())
        textWidget.setRedraw(false);

      int topIndex= getTopIndex();
      Point selection= getSelectedRange();
      setVisibleDocument(visibleDocument);
      Point currentSelection= getSelectedRange();
      if (currentSelection.x != selection.x || currentSelection.y != selection.y)
        setSelectedRange(selection.x, selection.y);
      setTopIndex(topIndex);

    } finally {
      if (textWidget != null && !textWidget.isDisposed())
        textWidget.setRedraw(true);
    }
  }

  /**
   * Hides the given range by collapsing it. If requested, a redraw request is issued.
   *
   * @param offset the offset of the range to hide
   * @param length the length of the range to hide
   * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise
   * @throws BadLocationException in case the range is invalid
   */
  private void collapse(int offset, int length, boolean fireRedraw) throws BadLocationException {
    ProjectionDocument projection= null;

    IDocument visibleDocument= getVisibleDocument();
    if (visibleDocument instanceof ProjectionDocument)
      projection= (ProjectionDocument) visibleDocument;
    else {
      IDocument master= getDocument();
      IDocument slave= createSlaveDocument(getDocument());
      if (slave instanceof ProjectionDocument) {
        projection= (ProjectionDocument) slave;
        addMasterDocumentRange(projection, 0, master.getLength());
        replaceVisibleDocument(projection);
      }
    }

    if (projection != null)
      removeMasterDocumentRange(projection, offset, length);

    if (projection != null && fireRedraw) {
      // repaint line above to get the folding box
      IDocument document= getDocument();
      int line= document.getLineOfOffset(offset);
      if (line > 0) {
        IRegion info= document.getLineInformation(line - 1);
        internalInvalidateTextPresentation(info.getOffset(), info.getLength());
      }
    }
  }

  /**
   * Makes the given range visible again while not changing the folding state of any contained
   * ranges. If requested, a redraw request is issued.
   *
   * @param offset the offset of the range to be expanded
   * @param length the length of the range to be expanded
   * @param fireRedraw <code>true</code> if a redraw request should be issued,
   *        <code>false</code> otherwise
   * @throws BadLocationException in case the range is invalid
   */
  private void expand(int offset, int length, boolean fireRedraw) throws BadLocationException {
    IDocument slave= getVisibleDocument();
    if (slave instanceof ProjectionDocument) {
      ProjectionDocument projection= (ProjectionDocument) slave;

      // expand
      addMasterDocumentRange(projection, offset, length);

      // collapse contained regions
      ProjectionAnnotation[] collapsed= computeCollapsedNestedAnnotations(offset, length);
      if (collapsed != null) {
        for (int i= 0; i < collapsed.length; i++) {
          IRegion[] regions= computeCollapsedRegions(fProjectionAnnotationModel.getPosition(collapsed[i]));
          if (regions != null)
            for (int j= 0; j < regions.length; j++)
              removeMasterDocumentRange(projection, regions[j].getOffset(), regions[j].getLength());
        }
      }

      // redraw if requested
      if (fireRedraw)
        internalInvalidateTextPresentation(offset, length);
    }
  }

  /**
   * Processes the request for catch up with the annotation model in the UI thread. If the current
   * thread is not the UI thread or there are pending catch up requests, a new request is posted.
   *
   * @param event the annotation model event
   */
  protected final void processCatchupRequest(AnnotationModelEvent event) {
    if (Display.getCurrent() != null) {
      boolean run= false;
      synchronized (fLock) {
        run= fPendingRequests.isEmpty();
      }
      if (run) {

        try {
          catchupWithProjectionAnnotationModel(event);
        } catch (BadLocationException x) {
          throw new IllegalArgumentException();
        }

      } else
        postCatchupRequest(event);
    } else {
      postCatchupRequest(event);
    }
  }

  /**
   * Posts the request for catch up with the annotation model into the UI thread.
   *
   * @param event the annotation model event
   */
  protected final void postCatchupRequest(final AnnotationModelEvent event) {
    synchronized (fLock) {
      fPendingRequests.add(event);
      if (fPendingRequests.size() == 1) {
        StyledText widget= getTextWidget();
        if (widget != null) {
          Display display= widget.getDisplay();
          if (display != null) {
            display.asyncExec(new Runnable() {
              public void run() {
                try {
                  while (true) {
                    AnnotationModelEvent ame= null;
                    synchronized (fLock) {
                      if (fPendingRequests.size() == 0)
                        return;
                      ame= (AnnotationModelEvent) fPendingRequests.remove(0);
                    }
                    catchupWithProjectionAnnotationModel(ame);
                  }
                } catch (BadLocationException x) {
                  try {
                    catchupWithProjectionAnnotationModel(null);
                  } catch (BadLocationException x1) {
                    throw new IllegalArgumentException();
                  } finally {
                    synchronized (fLock) {
                      fPendingRequests.clear();
                    }
                  }
                }
              }
            });
          }
        }
      }
    }
  }
 
  /**
   * Tests whether the visible document's master document
   * is identical to this viewer's document.
   *
   * @return <code>true</code> if the visible document's master is
   *       identical to this viewer's document
   * @since 3.1
   */
  private boolean isVisibleMasterDocumentSameAsDocument() {
    IDocument visibleDocument= getVisibleDocument();
    return (visibleDocument instanceof ProjectionDocument) && ((ProjectionDocument)visibleDocument).getMasterDocument() == getDocument();
  }

  /**
   * Adapts the slave visual document of this viewer to the changes described
   * in the annotation model event. When the event is <code>null</code>,
   * this is identical to a world change event.
   *
   * @param event the annotation model event or <code>null</code>
   * @exception BadLocationException in case the annotation model event is no longer in synchronization with the document
   */
  private void catchupWithProjectionAnnotationModel(AnnotationModelEvent event) throws BadLocationException {
   
    if (event == null || !isVisibleMasterDocumentSameAsDocument()) {

      fPendingAnnotationWorldChange= false;
      reinitializeProjection();

    } else if (event.isWorldChange()) {

      if (event.isValid()) {
        fPendingAnnotationWorldChange= false;
        reinitializeProjection();
      } else
        fPendingAnnotationWorldChange= true;

    } else if (fPendingAnnotationWorldChange) {
      if (event.isValid()) {
        fPendingAnnotationWorldChange= false;
        reinitializeProjection();
      }
    } else {
     
      Annotation[] addedAnnotations= event.getAddedAnnotations();
      Annotation[] changedAnnotation= event.getChangedAnnotations();
      Annotation[] removedAnnotations= event.getRemovedAnnotations();
     
      fCommandQueue= new ProjectionCommandQueue();
     
      boolean isRedrawing= redraws();
      int topIndex= isRedrawing ? getTopIndex() : -1;
     
      processDeletions(event, removedAnnotations, true);
      List coverage= new ArrayList();
      processChanges(addedAnnotations, true, coverage);
      processChanges(changedAnnotation, true, coverage);
     
      ProjectionCommandQueue commandQueue= fCommandQueue;
      fCommandQueue= null;
     
      if (commandQueue.passedRedrawCostsThreshold()) {
        setRedraw(false);
        try {
          executeProjectionCommands(commandQueue, false);
        } catch (IllegalArgumentException x) {
          reinitializeProjection();
        } finally {
          setRedraw(true, topIndex);
        }
      } else {
        try {
          boolean fireRedraw= !commandQueue.passedInvalidationCostsThreshold();
          executeProjectionCommands(commandQueue, fireRedraw);
          if (!fireRedraw)
            invalidateTextPresentation();
        } catch (IllegalArgumentException x) {
          reinitializeProjection();
        }
      }
    }
  }

  private void executeProjectionCommands(ProjectionCommandQueue commandQueue, boolean fireRedraw) throws BadLocationException {

    ProjectionCommand command;
    Iterator e= commandQueue.iterator();
    while (e.hasNext()) {
      command= (ProjectionCommand) e.next();
      switch (command.fType) {
        case ProjectionCommand.ADD:
          addMasterDocumentRange(command.fProjection, command.fOffset, command.fLength);
          break;
        case ProjectionCommand.REMOVE:
          removeMasterDocumentRange(command.fProjection, command.fOffset, command.fLength);
          break;
        case ProjectionCommand.INVALIDATE_PRESENTATION:
          if (fireRedraw)
            invalidateTextPresentation(command.fOffset, command.fLength);
          break;
      }
    }

    commandQueue.clear();
  }

  private boolean covers(int offset, int length, Position position) {
    if (!(position.offset == offset && position.length == length) && !position.isDeleted())
      return offset <= position.getOffset() && position.getOffset() + position.getLength() <= offset + length;
    return false;
  }

  private ProjectionAnnotation[] computeCollapsedNestedAnnotations(int offset, int length) {
    List annotations= new ArrayList(5);
    Iterator e= fProjectionAnnotationModel.getAnnotationIterator();
    while (e.hasNext()) {
      ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
      if (annotation.isCollapsed()) {
        Position position= fProjectionAnnotationModel.getPosition(annotation);
        if (position == null) {
          // annotation might already be deleted, we will be informed later on about this deletion
          continue;
        }
        if (covers(offset, length, position))
          annotations.add(annotation);
      }
    }

    if (annotations.size() > 0) {
      ProjectionAnnotation[] result= new ProjectionAnnotation[annotations.size()];
      annotations.toArray(result);
      return result;
    }

    return null;
  }

  private void internalInvalidateTextPresentation(int offset, int length) {
    if (fCommandQueue != null) {
      fCommandQueue.add(new ProjectionCommand(offset, length));
    } else {
      invalidateTextPresentation(offset, length);
    }
  }

  /*
   * We pass the removed annotation into this method for performance reasons only. Otherwise, they could be fetch from the event.
   */
  private void processDeletions(AnnotationModelEvent event, Annotation[] removedAnnotations, boolean fireRedraw) throws BadLocationException {
    for (int i= 0; i < removedAnnotations.length; i++) {
      ProjectionAnnotation annotation= (ProjectionAnnotation) removedAnnotations[i];
      if (annotation.isCollapsed()) {
        Position expanded= event.getPositionOfRemovedAnnotation(annotation);
        expand(expanded.getOffset(), expanded.getLength(), fireRedraw);
      }
    }
  }

  /**
   * Computes the region that must be collapsed when the given position is the
   * position of an expanded projection annotation.
   *
   * @param position the position
   * @return the range that must be collapsed
   */
  public IRegion computeCollapsedRegion(Position position) {
    try {
      IDocument document= getDocument();
      if (document == null)
        return null;
     
      int line= document.getLineOfOffset(position.getOffset());
      int offset= document.getLineOffset(line + 1);

      int length= position.getLength() - (offset - position.getOffset());
      if (length > 0)
        return new Region(offset, length);
    } catch (BadLocationException x) {
    }

    return null;
  }

  /**
   * Computes the regions that must be collapsed when the given position is
   * the position of an expanded projection annotation.
   *
   * @param position the position
   * @return the ranges that must be collapsed, or <code>null</code> if
   *         there are none
   * @since 3.1
   */
  IRegion[] computeCollapsedRegions(Position position) {
    try {
      IDocument document= getDocument();
      if (document == null)
        return null;

      if (position instanceof IProjectionPosition) {
        IProjectionPosition projPosition= (IProjectionPosition) position;
        return projPosition.computeProjectionRegions(document);
      }

      int line= document.getLineOfOffset(position.getOffset());
      int offset= document.getLineOffset(line + 1);

      int length= position.getLength() - (offset - position.getOffset());
      if (length > 0)
        return new IRegion[] {new Region(offset, length)};

      return null;
    } catch (BadLocationException x) {
      return null;
    }
  }

  /**
   * Computes the collapsed region anchor for the given position. Assuming
   * that the position is the position of an expanded projection annotation,
   * the anchor is the region that is still visible after the projection
   * annotation has been collapsed.
   *
   * @param position the position
   * @return the collapsed region anchor
   */
  public Position computeCollapsedRegionAnchor(Position position) {
    try {
      IDocument document= getDocument();
      if (document == null)
        return null;

      int captionOffset= position.getOffset();
      if (position instanceof IProjectionPosition)
        captionOffset+= ((IProjectionPosition) position).computeCaptionOffset(document);

      IRegion lineInfo= document.getLineInformationOfOffset(captionOffset);
      return new Position(lineInfo.getOffset() + lineInfo.getLength(), 0);
    } catch (BadLocationException x) {
    }
    return null;
  }

  /**
   * Labda4jdt Override original implementation to suppress limitation of returning
   * only lines which mapped 1:1 in slave and master document. Needed 1:n
   * to work properly
   */
  public int widgetLine2ModelLine(int widgetLine) {
    if (fInformationMapping == null)
      return widgetLine;
    try {
      return fInformationMapping.toOriginLines(widgetLine).getOffset();
    } catch (Exception e) {
    }
      return -1;
  }
 
  private void processChanges(Annotation[] annotations, boolean fireRedraw, List coverage) throws BadLocationException {
    for (int i= 0; i < annotations.length; i++) {
      ProjectionAnnotation annotation= (ProjectionAnnotation) annotations[i];
      Position position= fProjectionAnnotationModel.getPosition(annotation);

      if (position == null)
        continue;

      if (!covers(coverage, position)) {
        if (annotation.isCollapsed()) {
          coverage.add(position);
          IRegion[] regions= computeCollapsedRegions(position);
          if (regions != null)
            for (int j= 0; j < regions.length; j++)
              collapse(regions[j].getOffset(), regions[j].getLength(), fireRedraw);
        } else {
          //XXX: Lambda4jdt commented out original execution path
          //expand(position.getOffset(), position.getLength(), fireRedraw);

          //XXX: Lambda4jdt
          // Added hack to allow projection position to compute uncollapsed regions back
          // instead of using direct position.getOffset(), position.getLength()
          // It is used for case of clause folding : projection position hides additional content
          // before and after projection position region.
          // This chunk of code can be definately source of bugs, because improperly
          // changed IProjectionPosition model can expose back not a very same regions
          // that was originally collapsed
          IDocument visibleDocument = getVisibleDocument();
          if (position instanceof IProjectionPosition) {
            IDocument masterDocument = ((ProjectionDocument) visibleDocument)
                    .getMasterDocument();

            IProjectionPosition projectionPosition = (IProjectionPosition) position;
            IRegion[] regions = projectionPosition.computeProjectionRegions(masterDocument);
            if (regions != null) {
              // expand every recomputed region included in position
              for (int j = 0; j < regions.length; j++)
                expand(regions[j].getOffset(), regions[j].getLength(), fireRedraw);
            }
                        else
                          expand(position.getOffset(), position.getLength(), fireRedraw);// like original

          } else
                      expand(position.getOffset(), position.getLength(), fireRedraw);// like original
         
          //XXX Lambda4jdt end
        }
      }
    }
  }

  private boolean covers(List coverage, Position position) {
    Iterator e= coverage.iterator();
    while (e.hasNext()) {
      Position p= (Position) e.next();
      if (p.getOffset() <= position.getOffset() && position.getOffset() + position.getLength() <= p.getOffset() + p.getLength())
        return true;
    }
    return false;
  }

  /**
   * Forces this viewer to throw away any old state and to initialize its content
   * from its projection annotation model.
   *
   * @throws BadLocationException in case something goes wrong during initialization
   */
  public final void reinitializeProjection() throws BadLocationException {

    ProjectionDocument projection= null;

    ISlaveDocumentManager manager= getSlaveDocumentManager();
    if (manager != null) {
      IDocument master= getDocument();
      if (master != null) {
        IDocument slave= manager.createSlaveDocument(master);
        if (slave instanceof ProjectionDocument) {
          projection= (ProjectionDocument) slave;
          addMasterDocumentRange(projection, 0, master.getLength());
        }
      }
    }

    if (projection != null) {
      Iterator e= fProjectionAnnotationModel.getAnnotationIterator();
      while (e.hasNext()) {
        ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
        if (annotation.isCollapsed()) {
          Position position= fProjectionAnnotationModel.getPosition(annotation);
          if (position != null) {
            IRegion[] regions= computeCollapsedRegions(position);
            if (regions != null)
              for (int i= 0; i < regions.length; i++)
                removeMasterDocumentRange(projection, regions[i].getOffset(), regions[i].getLength());
          }
        }
      }

    }

    replaceVisibleDocument(projection);
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#handleVerifyEvent(org.eclipse.swt.events.VerifyEvent)
   */
  protected void handleVerifyEvent(VerifyEvent e) {
    IRegion modelRange= event2ModelRange(e);
    if (exposeModelRange(modelRange))
      e.doit= false;
    else
      super.handleVerifyEvent(e);
  }

  /**
   * Adds the give column as last column to this viewer's vertical ruler.
   *
   * @param column the column to be added
   */
  public void addVerticalRulerColumn(IVerticalRulerColumn column) {
    IVerticalRuler ruler= getVerticalRuler();
    if (ruler instanceof CompositeRuler) {
      CompositeRuler compositeRuler= (CompositeRuler) ruler;
      compositeRuler.addDecorator(99, column);
    }
  }

  /**
   * Removes the give column from this viewer's vertical ruler.
   *
   * @param column the column to be removed
   */
  public void removeVerticalRulerColumn(IVerticalRulerColumn column) {
    IVerticalRuler ruler= getVerticalRuler();
    if (ruler instanceof CompositeRuler) {
      CompositeRuler compositeRuler= (CompositeRuler) ruler;
      compositeRuler.removeDecorator(column);
    }
  }

  /*
   * @see org.eclipse.jface.text.ITextViewerExtension5#exposeModelRange(org.eclipse.jface.text.IRegion)
   */
  public boolean exposeModelRange(IRegion modelRange) {
    if (isProjectionMode())
      return fProjectionAnnotationModel.expandAll(modelRange.getOffset(), modelRange.getLength());

    if (!overlapsWithVisibleRegion(modelRange.getOffset(), modelRange.getLength())) {
      resetVisibleRegion();
      return true;
    }

    return false;
  }

  /*
   * @see org.eclipse.jface.text.source.SourceViewer#setRangeIndication(int, int, boolean)
   */
  public void setRangeIndication(int offset, int length, boolean moveCursor) {
    IRegion rangeIndication= getRangeIndication();
    if (moveCursor && fProjectionAnnotationModel != null && (rangeIndication == null || offset != rangeIndication.getOffset() || length != rangeIndication.getLength())) {
      List expand= new ArrayList(2);
      // expand the immediate effected collapsed regions
      Iterator iterator= fProjectionAnnotationModel.getAnnotationIterator();
      while (iterator.hasNext()) {
        ProjectionAnnotation annotation= (ProjectionAnnotation)iterator.next();
        if (annotation.isCollapsed() && willAutoExpand(fProjectionAnnotationModel.getPosition(annotation), offset, length))
          expand.add(annotation);
      }

      if (!expand.isEmpty()) {
        Iterator e= expand.iterator();
        while (e.hasNext())
          fProjectionAnnotationModel.expand((Annotation)e.next());
      }
    }
    super.setRangeIndication(offset, length, moveCursor);
  }

  private boolean willAutoExpand(Position position, int offset, int length) {
    if (position == null || position.isDeleted())
      return false;
    // right or left boundary
    if (position.getOffset() == offset || position.getOffset() + position.getLength() == offset + length)
      return true;
    // completely embedded in given position
    if (position.getOffset() < offset && offset + length < position.getOffset() + position.getLength())
      return true;
    return false;
  }

  /*
   * @see org.eclipse.jface.text.source.SourceViewer#handleDispose()
   * @since 3.0
   */
  protected void handleDispose() {
    fWasProjectionEnabled= false;
    super.handleDispose();
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
   */
  protected void handleVisibleDocumentChanged(DocumentEvent event) {
    if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) {
      ProjectionDocumentEvent e= (ProjectionDocumentEvent) event;

      DocumentEvent master= e.getMasterEvent();
      if (master != null)
        fReplaceVisibleDocumentExecutionTrigger= master.getDocument();

      try {

        int replaceLength= e.getText() == null ? 0 : e.getText().length();
        if (ProjectionDocumentEvent.PROJECTION_CHANGE == e.getChangeType()) {
          if (e.getLength() == 0 && replaceLength != 0)
            fProjectionAnnotationModel.expandAll(e.getMasterOffset(), e.getMasterLength());
        } else if (master != null && (replaceLength > 0 || fDeletedLines > 1)) {
          try {
            int numberOfLines= e.getDocument().getNumberOfLines(e.getOffset(), replaceLength);
            if (numberOfLines > 1 || fDeletedLines > 1)
              fProjectionAnnotationModel.expandAll(master.getOffset(), master.getLength());
          } catch (BadLocationException x) {
          }
        }

      } finally {
        fReplaceVisibleDocumentExecutionTrigger= null;
      }

    }
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
   * @since 3.1
   */
  protected void handleVisibleDocumentAboutToBeChanged(DocumentEvent event) {
    if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) {
      int deletedLines;
      try {
        deletedLines= event.getDocument().getNumberOfLines(event.getOffset(), event.getLength());
      } catch (BadLocationException e1) {
        deletedLines= 0;
      }
      fDeletedLines= deletedLines;
    }
  }

  /*
   * @see org.eclipse.jface.text.ITextViewerExtension5#getCoveredModelRanges(org.eclipse.jface.text.IRegion)
   */
  public IRegion[] getCoveredModelRanges(IRegion modelRange) {
    if (fInformationMapping == null)
      return new IRegion[] { new Region(modelRange.getOffset(), modelRange.getLength()) };

    if (fInformationMapping instanceof IDocumentInformationMappingExtension) {
      IDocumentInformationMappingExtension extension= (IDocumentInformationMappingExtension) fInformationMapping;
      try {
        return extension.getExactCoverage(modelRange);
      } catch (BadLocationException x) {
      }
    }

    return null;
  }

  /*
   * @see org.eclipse.jface.text.ITextOperationTarget#doOperation(int)
   */
  public void doOperation(int operation) {
    switch (operation) {
      case TOGGLE:
        if (canDoOperation(TOGGLE)) {
          if (!isProjectionMode()) {
            enableProjection();
          } else {
            expandAll();
            disableProjection();
          }
          return;
        }
    }

    if (!isProjectionMode()) {
      super.doOperation(operation);
      return;
    }

    StyledText textWidget= getTextWidget();
    if (textWidget == null)
      return;

    Point selection= null;
    switch (operation) {

      case CUT:

        if (redraws()) {
          selection= getSelectedRange();
          if (exposeModelRange(new Region(selection.x, selection.y)))
            return;
         
          if (selection.y == 0)
            copyMarkedRegion(true);
          else
            copyToClipboard(selection.x, selection.y, true, textWidget);

          selection= textWidget.getSelectionRange();
          fireSelectionChanged(selection.x, selection.y);
        }
        break;

      case COPY:

        if (redraws()) {
          selection= getSelectedRange();
          if (selection.y == 0)
            copyMarkedRegion(false);
          else
            copyToClipboard(selection.x, selection.y, false, textWidget);
        }
        break;

      case DELETE:

        if (redraws()) {
          try {
            selection= getSelectedRange();
            Point widgetSelection= textWidget.getSelectionRange();
            if (selection.y == 0 || selection.y == widgetSelection.y)
              getTextWidget().invokeAction(ST.DELETE_NEXT);
            else
              deleteTextRange(selection.x, selection.y, textWidget);

            selection= textWidget.getSelectionRange();
            fireSelectionChanged(selection.x, selection.y);

          } catch (BadLocationException x) {
            // ignore
          }
        }
        break;


      case EXPAND_ALL:
        if (redraws())
          expandAll();
        break;

      case EXPAND:
        if (redraws()) {
          expand();
        }
        break;

      case COLLAPSE_ALL:
        if (redraws())
          collapseAll();
        break;
       
      case COLLAPSE:
        if (redraws()) {
          collapse();
        }
        break;

      default:
        super.doOperation(operation);
    }
  }

  /*
   * @see org.eclipse.jface.text.source.SourceViewer#canDoOperation(int)
   */
  public boolean canDoOperation(int operation) {

    switch (operation) {
      case COLLAPSE:
      case COLLAPSE_ALL:
      case EXPAND:
      case EXPAND_ALL:
        return isProjectionMode();
      case TOGGLE:
        return isProjectionMode() || !isSegmented();
    }

    return super.canDoOperation(operation);
  }

  private boolean isSegmented() {
    IDocument document= getDocument();
    int length= document == null ? 0 : document.getLength();
    IRegion visible= getModelCoverage();
    boolean isSegmented= visible != null && !visible.equals(new Region(0, length));
    return isSegmented;
  }

  private IRegion getMarkedRegion() {
    if (getTextWidget() == null)
      return null;

    if (fMarkPosition == null || fMarkPosition.isDeleted())
      return null;

    int start= fMarkPosition.getOffset();
    int end= getSelectedRange().x;

    return start > end ? new Region (end, start - end) : new Region(start, end - start);
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#copyMarkedRegion(boolean)
   */
  protected void copyMarkedRegion(boolean delete) {
    IRegion markedRegion= getMarkedRegion();
    if (markedRegion != null)
      copyToClipboard(markedRegion.getOffset(), markedRegion.getLength(), delete, getTextWidget());
  }

  private void copyToClipboard(int offset, int length, boolean delete, StyledText textWidget) {

    String copyText= null;

    try {
      IDocument document= getDocument();
      copyText= document.get(offset, length);
    } catch (BadLocationException ex) {
      // XXX: should log here, but JFace Text has no Log
      // As a fallback solution let the widget handle this
      textWidget.copy();
    }

    if (copyText != null && copyText.equals(textWidget.getSelectionText())) {
      /*
       * XXX: Reduce pain of https://bugs.eclipse.org/bugs/show_bug.cgi?id=64498
       * by letting the widget handle the copy operation in this special case.
       */
      textWidget.copy();
    } else if (copyText != null) {

      Clipboard clipboard= new Clipboard(textWidget.getDisplay());

      try {
        Transfer[] dataTypes= new Transfer[] { TextTransfer.getInstance() };
        Object[] data= new Object[] { copyText };
        try {
          clipboard.setContents(data, dataTypes);
        } catch (SWTError e) {
          if (e.code != DND.ERROR_CANNOT_SET_CLIPBOARD)
            throw e;
          /*
           * TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59459
           * we should either log and/or inform the user
           * silently fail for now.
           */
          return;
        }

      } finally {
        clipboard.dispose();
      }
    }

    if (delete) {
      try {
        deleteTextRange(offset, length, textWidget);
      } catch (BadLocationException x) {
        // XXX: should log here, but JFace Text has no Log
      }
    }
  }

  private void deleteTextRange(int offset, int length, StyledText textWidget) throws BadLocationException {
    getDocument().replace(offset, length, ""); //$NON-NLS-1$
    int widgetCaret= modelOffset2WidgetOffset(offset);
    if (widgetCaret > -1)
      textWidget.setSelection(widgetCaret);
  }

  /**
   * Adapts the behavior of the super class to respect line based folding.
   *
   * @param widgetSelection the widget selection
   * @return the model selection while respecting line based folding
   */
  protected Point widgetSelection2ModelSelection(Point widgetSelection) {

    if (!isProjectionMode())
      return super.widgetSelection2ModelSelection(widgetSelection);

    /*
     * There is one requirement that governs preservation of logical
     * positions:
     *
     * 1) a selection with widget_length == 0 should never expand to have
     * model_length > 0.
     *
     * There are a number of ambiguities to resolve with projection regions.
     * A projected region P has a widget-length of zero. Its widget offset
     * may interact with the selection S in various ways:
     *
     * A) P.widget_offset lies at the caret, S.widget_length is zero.
     * Requirement 1 applies. S is *behind* P (done so by widgetRange2ModelRange).
     *
     * B) P.widget_offset lies inside the widget selection. This case is
     * easy: P is included in S, which is automatically done so by
     * widgetRange2ModelRange.
     *
     * C) P.widget_offset lies at S.widget_end: This is
     * arguable - our policy is to include P if it belongs to a projection
     * annotation that overlaps with the widget selection.
     *
     * D) P.widget_offset lies at S.widget_offset: Arguable - our policy
     * is to include P if it belongs to a projection annotation that
     * overlaps with the widget selection
     */
    IRegion modelSelection= widgetRange2ModelRange(new Region(widgetSelection.x, widgetSelection.y));
    if (modelSelection == null)
      return null;

    int modelOffset= modelSelection.getOffset();
    int modelEndOffset= modelOffset + modelSelection.getLength();

    /* Case A: never expand a zero-length selection. S is *behind* P. */
    if (widgetSelection.y == 0)
      return new Point(modelEndOffset, 0);

    int widgetSelectionExclusiveEnd= widgetSelection.x + widgetSelection.y;
    Position[] annotationPositions= computeOverlappingAnnotationPositions(modelSelection);
    for (int i= 0; i < annotationPositions.length; i++) {
      IRegion[] regions= computeCollapsedRegions(annotationPositions[i]);
      if (regions == null)
        continue;
      for (int j= 0; j < regions.length; j++) {
        IRegion modelRange= regions[j];
        IRegion widgetRange= modelRange2ClosestWidgetRange(modelRange);
        // only take collapsed ranges, i.e. widget length is 0
        if (widgetRange != null && widgetRange.getLength() == 0) {
          int widgetOffset= widgetRange.getOffset();
          // D) region is collapsed at S.widget_offset
          if (widgetOffset == widgetSelection.x)
            modelOffset= Math.min(modelOffset, modelRange.getOffset());
          // C) region is collapsed at S.widget_end
          else if (widgetOffset == widgetSelectionExclusiveEnd)
            modelEndOffset= Math.max(modelEndOffset, modelRange.getOffset() + modelRange.getLength());
        }
      }
    }
    return new Point(modelOffset, modelEndOffset - modelOffset);
  }

  /**
   * Returns the positions of all annotations that intersect with
   * <code>modelSelection</code> and that are at least partly visible.
   * @param modelSelection a model range
   * @return the positions of all annotations that intersect with
   *         <code>modelSelection</code>
   * @since 3.1
   */
  private Position[] computeOverlappingAnnotationPositions(IRegion modelSelection) {
    List positions= new ArrayList();
    for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
      ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
      Position position= fProjectionAnnotationModel.getPosition(annotation);
      if (position != null && position.overlapsWith(modelSelection.getOffset(), modelSelection.getLength()) && modelRange2WidgetRange(position) != null)
        positions.add(position);
    }
    return (Position[]) positions.toArray(new Position[positions.size()]);
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#getFindReplaceDocumentAdapter()
   */
  protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() {
    if (fFindReplaceDocumentAdapter == null) {
      IDocument document= isProjectionMode() ? getDocument() : getVisibleDocument();
      fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(document);
    }
    return fFindReplaceDocumentAdapter;
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#findAndSelect(int, java.lang.String, boolean, boolean, boolean, boolean)
   */
  protected int findAndSelect(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) {

    if (!isProjectionMode())
      return super.findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);

    StyledText textWidget= getTextWidget();
    if (textWidget == null)
      return -1;

    try {

      IRegion matchRegion= getFindReplaceDocumentAdapter().find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
      if (matchRegion != null) {
        exposeModelRange(matchRegion);
        revealRange(matchRegion.getOffset(), matchRegion.getLength());
        setSelectedRange(matchRegion.getOffset(), matchRegion.getLength());
        return matchRegion.getOffset();
      }

    } catch (BadLocationException x) {
    }

    return -1;
  }

  /*
   * @see org.eclipse.jface.text.TextViewer#findAndSelectInRange(int, java.lang.String, boolean, boolean, boolean, int, int, boolean)
   */
  protected int findAndSelectInRange(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, int rangeOffset, int rangeLength, boolean regExSearch) {

    if (!isProjectionMode())
      return super.findAndSelectInRange(startPosition, findString, forwardSearch, caseSensitive, wholeWord, rangeOffset, rangeLength, regExSearch);

    StyledText textWidget= getTextWidget();
    if (textWidget == null)
      return -1;

    try {

      int modelOffset= startPosition;
      if (forwardSearch && (startPosition == -1 || startPosition < rangeOffset)) {
        modelOffset= rangeOffset;
      } else if (!forwardSearch && (startPosition == -1 || startPosition > rangeOffset + rangeLength)) {
        modelOffset= rangeOffset + rangeLength;
      }

      IRegion matchRegion= getFindReplaceDocumentAdapter().find(modelOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
      if (matchRegion != null) {
        int offset= matchRegion.getOffset();
        int length= matchRegion.getLength();
        if (rangeOffset <= offset && offset + length <= rangeOffset + rangeLength) {
          exposeModelRange(matchRegion);
          revealRange(offset, length);
          setSelectedRange(offset, length);
          return offset;
        }
      }

    } catch (BadLocationException x) {
    }

    return -1;
  }
}
TOP

Related Classes of org.eclipse.jface.text.source.projection.ProjectionViewer$ProjectionCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.