Mercurial > projects > dwt-addons
diff dwtx/jface/text/source/projection/ProjectionViewer.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/source/projection/ProjectionViewer.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,1788 @@ +/******************************************************************************* + * 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.source.projection.ProjectionViewer; + +import dwt.dwthelper.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import dwt.DWTError; +import dwt.custom.ST; +import dwt.custom.StyledText; +import dwt.dnd.Clipboard; +import dwt.dnd.DND; +import dwt.dnd.TextTransfer; +import dwt.dnd.Transfer; +import dwt.events.VerifyEvent; +import dwt.graphics.Point; +import dwt.widgets.Composite; +import dwt.widgets.Display; +import dwtx.core.runtime.Assert; +import dwtx.jface.text.BadLocationException; +import dwtx.jface.text.DocumentEvent; +import dwtx.jface.text.FindReplaceDocumentAdapter; +import dwtx.jface.text.IDocument; +import dwtx.jface.text.IDocumentInformationMappingExtension; +import dwtx.jface.text.IDocumentListener; +import dwtx.jface.text.IRegion; +import dwtx.jface.text.ISlaveDocumentManager; +import dwtx.jface.text.ITextViewerExtension5; +import dwtx.jface.text.Position; +import dwtx.jface.text.Region; +import dwtx.jface.text.TextUtilities; +import dwtx.jface.text.projection.ProjectionDocument; +import dwtx.jface.text.projection.ProjectionDocumentEvent; +import dwtx.jface.text.projection.ProjectionDocumentManager; +import dwtx.jface.text.source.Annotation; +import dwtx.jface.text.source.AnnotationModelEvent; +import dwtx.jface.text.source.CompositeRuler; +import dwtx.jface.text.source.IAnnotationModel; +import dwtx.jface.text.source.IAnnotationModelExtension; +import dwtx.jface.text.source.IAnnotationModelListener; +import dwtx.jface.text.source.IAnnotationModelListenerExtension; +import dwtx.jface.text.source.IOverviewRuler; +import dwtx.jface.text.source.IVerticalRuler; +import dwtx.jface.text.source.IVerticalRulerColumn; +import dwtx.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 : SourceViewer , 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 : IAnnotationModelListener, IAnnotationModelListenerExtension { + + /* + * @see dwtx.jface.text.source.IAnnotationModelListener#modelChanged(dwtx.jface.text.source.IAnnotationModel) + */ + public void modelChanged(IAnnotationModel model) { + processModelChanged(model, null); + } + + /* + * @see dwtx.jface.text.source.IAnnotationModelListenerExtension#modelChanged(dwtx.jface.text.source.AnnotationModelEvent) + */ + public void modelChanged(AnnotationModelEvent event) { + processModelChanged(event.getAnnotationModel(), event); + } + + private void processModelChanged(IAnnotationModel model, AnnotationModelEvent event) { + if (model is fProjectionAnnotationModel) { + + if (fProjectionSummary !is null) + fProjectionSummary.updateSummaries(); + processCatchupRequest(event); + + } else if (model is getAnnotationModel() && fProjectionSummary !is null) + fProjectionSummary.updateSummaries(); + } + } + + /** + * Executes the 'replaceVisibleDocument' operation when called the first time. Self-destructs afterwards. + */ + private class ReplaceVisibleDocumentExecutor : 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 !is null && fSlaveDocument !is null) { + fExecutionTrigger= executionTrigger; + fExecutionTrigger.addDocumentListener(this); + } + } + + /* + * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent) + */ + public void documentAboutToBeChanged(DocumentEvent event) { + } + + /* + * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.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 is null ? 0 : gaps.length; + } catch (BadLocationException x) { + } + break; + } + case REMOVE: { + try { + IRegion[] fragments= fProjection.computeProjectedMasterRegions(fOffset, fLength); + return fragments is 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; + } + + bool passedRedrawCostsThreshold() { + if (fExpectedExecutionCosts is -1) + computeExpectedExecutionCosts(); + return fExpectedExecutionCosts > REDRAW_COSTS; + } + + bool passedInvalidationCostsThreshold() { + if (fExpectedExecutionCosts is -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 bool fPendingAnnotationWorldChange= false; + /** Indication whether projection changes in the visible document should be considered. */ + private bool 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 bool 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 DWT 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 DWT style bits + */ + public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, bool 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 dwtx.jface.text.source.SourceViewer#setDocument(dwtx.jface.text.IDocument, dwtx.jface.text.source.IAnnotationModel, int, int) + */ + public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) { + bool wasProjectionEnabled= false; + + synchronized (fLock) { + fPendingRequests.clear(); + } + + if (fProjectionAnnotationModel !is null) { + wasProjectionEnabled= removeProjectionAnnotationModel(getVisualAnnotationModel()) !is null; + fProjectionAnnotationModel= null; + } + + super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength); + + if (wasProjectionEnabled && document !is null) + enableProjection(); + } + + /* + * @see dwtx.jface.text.source.SourceViewer#createVisualAnnotationModel(dwtx.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 dwtx.jface.text.TextViewer#createSlaveDocumentManager() + */ + protected ISlaveDocumentManager createSlaveDocumentManager() { + return new ProjectionDocumentManager(); + } + + /* + * @see dwtx.jface.text.TextViewer#updateSlaveDocument(dwtx.jface.text.IDocument, int, int) + */ + protected bool 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 is 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 !is null) { + fProjectionListeners.remove(listener); + if (fProjectionListeners.size() is 0) + fProjectionListeners= null; + } + } + + /** + * Notifies all registered projection listeners + * that projection mode has been enabled. + */ + protected void fireProjectionEnabled() { + if (fProjectionListeners !is 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 !is 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 bool isProjectionMode() { + return getProjectionAnnotationModel() !is 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 is 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 !is null && touches(selection, position)) + if (found is null || position.includes(found.offset) && position.includes(found.offset + found.length)) { + found= position; + bestMatch= annotation; + } + } + } + + if (bestMatch !is null) { + fProjectionAnnotationModel.expand(bestMatch); + revealRange(selection.x, selection.y); + } + } + } + + private bool touches(Point selection, Position position) { + return position.overlapsWith(selection.x, selection.y) || selection.y is 0 && position.offset + position.length is 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 !is null && touches(selection, position)) + if (found is null || found.includes(position.offset) && found.includes(position.offset + position.length)) { + found= position; + bestMatch= annotation; + } + } + } + + if (bestMatch !is null) { + fProjectionAnnotationModel.collapse(bestMatch); + revealRange(selection.x, selection.y); + } + } + } + + /* + * @since 3.2 + */ + private void collapseAll() { + int offset= 0; + IDocument doc= getDocument(); + int length= doc is 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 !is 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 + int end= offset + length; + offset= toLineStart(projection.getMasterDocument(), offset, false); + length= toLineStart(projection.getMasterDocument(), end, true) - offset; + 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 !is 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 + int end= offset + length; + offset= toLineStart(projection.getMasterDocument(), offset, false); + length= toLineStart(projection.getMasterDocument(), end, true) - offset; + projection.removeMasterDocumentRange(offset, length); + } finally { + fHandleProjectionChanges= true; + } + } + } + + /** + * Returns the first line offset <= <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 >= <code>offset</code> + * @throws BadLocationException if the offset is invalid + * @since 3.2 + */ + private int toLineStart(IDocument document, int offset, bool testLastLine) throws BadLocationException { + if (document is null) + return offset; + + if (testLastLine && offset >= document.getLineInformationOfOffset(document.getLength() - 1).getOffset()) + return offset; + + return document.getLineInformationOfOffset(offset).getOffset(); + } + + /* + * @see dwtx.jface.text.TextViewer#setVisibleRegion(int, int) + */ + public void setVisibleRegion(int start, int length) { + fWasProjectionEnabled= isProjectionMode(); + disableProjection(); + super.setVisibleRegion(start, length); + } + + /* + * @see dwtx.jface.text.TextViewer#setVisibleDocument(dwtx.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 dwtx.jface.text.TextViewer#resetVisibleRegion() + */ + public void resetVisibleRegion() { + super.resetVisibleRegion(); + if (fWasProjectionEnabled) + enableProjection(); + } + + /* + * @see dwtx.jface.text.ITextViewer#getVisibleRegion() + */ + public IRegion getVisibleRegion() { + disableProjection(); + IRegion visibleRegion= getModelCoverage(); + if (visibleRegion is null) + visibleRegion= new Region(0, 0); + + return visibleRegion; + } + + /* + * @see dwtx.jface.text.ITextViewer#overlapsWithVisibleRegion(int,int) + */ + public bool overlapsWithVisibleRegion(int offset, int length) { + disableProjection(); + IRegion coverage= getModelCoverage(); + if (coverage is null) + return false; + + bool appending= (offset is coverage.getOffset() + coverage.getLength()) && length is 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 !is null) { + ReplaceVisibleDocumentExecutor executor= new ReplaceVisibleDocumentExecutor(slave); + executor.install(fReplaceVisibleDocumentExecutionTrigger); + } else + executeReplaceVisibleDocument(slave); + } + + + private void executeReplaceVisibleDocument(IDocument visibleDocument) { + StyledText textWidget= getTextWidget(); + try { + if (textWidget !is null && !textWidget.isDisposed()) + textWidget.setRedraw(false); + + int topIndex= getTopIndex(); + Point selection= getSelectedRange(); + setVisibleDocument(visibleDocument); + Point currentSelection= getSelectedRange(); + if (currentSelection.x !is selection.x || currentSelection.y !is selection.y) + setSelectedRange(selection.x, selection.y); + setTopIndex(topIndex); + + } finally { + if (textWidget !is 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, bool 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 !is null) + removeMasterDocumentRange(projection, offset, length); + + if (projection !is 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, bool 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 !is null) { + for (int i= 0; i < collapsed.length; i++) { + IRegion[] regions= computeCollapsedRegions(fProjectionAnnotationModel.getPosition(collapsed[i])); + if (regions !is 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() !is null) { + bool 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() is 1) { + StyledText widget= getTextWidget(); + if (widget !is null) { + Display display= widget.getDisplay(); + if (display !is null) { + display.asyncExec(new Runnable() { + public void run() { + try { + while (true) { + AnnotationModelEvent ame= null; + synchronized (fLock) { + if (fPendingRequests.size() is 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 bool isVisibleMasterDocumentSameAsDocument() { + IDocument visibleDocument= getVisibleDocument(); + return (visibleDocument instanceof ProjectionDocument) && ((ProjectionDocument)visibleDocument).getMasterDocument() is 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 is 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(); + + bool 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 { + bool fireRedraw= !commandQueue.passedInvalidationCostsThreshold(); + executeProjectionCommands(commandQueue, fireRedraw); + if (!fireRedraw) + invalidateTextPresentation(); + } catch (IllegalArgumentException x) { + reinitializeProjection(); + } + } + } + } + + private void executeProjectionCommands(ProjectionCommandQueue commandQueue, bool 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 bool covers(int offset, int length, Position position) { + if (!(position.offset is offset && position.length is 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 is 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 !is 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, bool 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 is 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 is 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 is 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; + } + + private void processChanges(Annotation[] annotations, bool 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 is null) + continue; + + if (!covers(coverage, position)) { + if (annotation.isCollapsed()) { + coverage.add(position); + IRegion[] regions= computeCollapsedRegions(position); + if (regions !is null) + for (int j= 0; j < regions.length; j++) + collapse(regions[j].getOffset(), regions[j].getLength(), fireRedraw); + } else { + expand(position.getOffset(), position.getLength(), fireRedraw); + } + } + } + } + + private bool 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 !is null) { + IDocument master= getDocument(); + if (master !is null) { + IDocument slave= manager.createSlaveDocument(master); + if (slave instanceof ProjectionDocument) { + projection= (ProjectionDocument) slave; + addMasterDocumentRange(projection, 0, master.getLength()); + } + } + } + + if (projection !is null) { + Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); + while (e.hasNext()) { + ProjectionAnnotation annotation= (ProjectionAnnotation) e.next(); + if (annotation.isCollapsed()) { + Position position= fProjectionAnnotationModel.getPosition(annotation); + if (position !is null) { + IRegion[] regions= computeCollapsedRegions(position); + if (regions !is null) + for (int i= 0; i < regions.length; i++) + removeMasterDocumentRange(projection, regions[i].getOffset(), regions[i].getLength()); + } + } + } + + } + + replaceVisibleDocument(projection); + } + + /* + * @see dwtx.jface.text.TextViewer#handleVerifyEvent(dwt.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 dwtx.jface.text.ITextViewerExtension5#exposeModelRange(dwtx.jface.text.IRegion) + */ + public bool exposeModelRange(IRegion modelRange) { + if (isProjectionMode()) + return fProjectionAnnotationModel.expandAll(modelRange.getOffset(), modelRange.getLength()); + + if (!overlapsWithVisibleRegion(modelRange.getOffset(), modelRange.getLength())) { + resetVisibleRegion(); + return true; + } + + return false; + } + + /* + * @see dwtx.jface.text.source.SourceViewer#setRangeIndication(int, int, bool) + */ + public void setRangeIndication(int offset, int length, bool moveCursor) { + IRegion rangeIndication= getRangeIndication(); + if (moveCursor && fProjectionAnnotationModel !is null && (rangeIndication is null || offset !is rangeIndication.getOffset() || length !is 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 bool willAutoExpand(Position position, int offset, int length) { + if (position is null || position.isDeleted()) + return false; + // right or left boundary + if (position.getOffset() is offset || position.getOffset() + position.getLength() is offset + length) + return true; + // completely embedded in given position + if (position.getOffset() < offset && offset + length < position.getOffset() + position.getLength()) + return true; + return false; + } + + /* + * @see dwtx.jface.text.source.SourceViewer#handleDispose() + * @since 3.0 + */ + protected void handleDispose() { + fWasProjectionEnabled= false; + super.handleDispose(); + } + + /* + * @see dwtx.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent) + */ + protected void handleVisibleDocumentChanged(DocumentEvent event) { + if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) { + ProjectionDocumentEvent e= (ProjectionDocumentEvent) event; + + DocumentEvent master= e.getMasterEvent(); + if (master !is null) + fReplaceVisibleDocumentExecutionTrigger= master.getDocument(); + + try { + + int replaceLength= e.getText() is null ? 0 : e.getText().length(); + if (ProjectionDocumentEvent.PROJECTION_CHANGE is e.getChangeType()) { + if (e.getLength() is 0 && replaceLength !is 0) + fProjectionAnnotationModel.expandAll(e.getMasterOffset(), e.getMasterLength()); + } else if (master !is 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 dwtx.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(dwtx.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 dwtx.jface.text.ITextViewerExtension5#getCoveredModelRanges(dwtx.jface.text.IRegion) + */ + public IRegion[] getCoveredModelRanges(IRegion modelRange) { + if (fInformationMapping is 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 dwtx.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 is 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 is 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 is 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 is 0 || selection.y is 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 dwtx.jface.text.source.SourceViewer#canDoOperation(int) + */ + public bool 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 bool isSegmented() { + IDocument document= getDocument(); + int length= document is null ? 0 : document.getLength(); + IRegion visible= getModelCoverage(); + bool isSegmented= visible !is null && !visible.equals(new Region(0, length)); + return isSegmented; + } + + private IRegion getMarkedRegion() { + if (getTextWidget() is null) + return null; + + if (fMarkPosition is 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 dwtx.jface.text.TextViewer#copyMarkedRegion(bool) + */ + protected void copyMarkedRegion(bool delete) { + IRegion markedRegion= getMarkedRegion(); + if (markedRegion !is null) + copyToClipboard(markedRegion.getOffset(), markedRegion.getLength(), delete, getTextWidget()); + } + + private void copyToClipboard(int offset, int length, bool 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 !is 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 !is 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 (DWTError e) { + if (e.code !is 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 is 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 is 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 is 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 is 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 !is null && widgetRange.getLength() is 0) { + int widgetOffset= widgetRange.getOffset(); + // D) region is collapsed at S.widget_offset + if (widgetOffset is widgetSelection.x) + modelOffset= Math.min(modelOffset, modelRange.getOffset()); + // C) region is collapsed at S.widget_end + else if (widgetOffset is 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 !is null && position.overlapsWith(modelSelection.getOffset(), modelSelection.getLength()) && modelRange2WidgetRange(position) !is null) + positions.add(position); + } + return (Position[]) positions.toArray(new Position[positions.size()]); + } + + /* + * @see dwtx.jface.text.TextViewer#getFindReplaceDocumentAdapter() + */ + protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() { + if (fFindReplaceDocumentAdapter is null) { + IDocument document= isProjectionMode() ? getDocument() : getVisibleDocument(); + fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(document); + } + return fFindReplaceDocumentAdapter; + } + + /* + * @see dwtx.jface.text.TextViewer#findAndSelect(int, java.lang.String, bool, bool, bool, bool) + */ + protected int findAndSelect(int startPosition, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch) { + + if (!isProjectionMode()) + return super.findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); + + StyledText textWidget= getTextWidget(); + if (textWidget is null) + return -1; + + try { + + IRegion matchRegion= getFindReplaceDocumentAdapter().find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); + if (matchRegion !is null) { + exposeModelRange(matchRegion); + revealRange(matchRegion.getOffset(), matchRegion.getLength()); + setSelectedRange(matchRegion.getOffset(), matchRegion.getLength()); + return matchRegion.getOffset(); + } + + } catch (BadLocationException x) { + } + + return -1; + } + + /* + * @see dwtx.jface.text.TextViewer#findAndSelectInRange(int, java.lang.String, bool, bool, bool, int, int, bool) + */ + protected int findAndSelectInRange(int startPosition, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, int rangeOffset, int rangeLength, bool regExSearch) { + + if (!isProjectionMode()) + return super.findAndSelectInRange(startPosition, findString, forwardSearch, caseSensitive, wholeWord, rangeOffset, rangeLength, regExSearch); + + StyledText textWidget= getTextWidget(); + if (textWidget is null) + return -1; + + try { + + int modelOffset= startPosition; + if (forwardSearch && (startPosition is -1 || startPosition < rangeOffset)) { + modelOffset= rangeOffset; + } else if (!forwardSearch && (startPosition is -1 || startPosition > rangeOffset + rangeLength)) { + modelOffset= rangeOffset + rangeLength; + } + + IRegion matchRegion= getFindReplaceDocumentAdapter().find(modelOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); + if (matchRegion !is 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; + } +}