| /******************************************************************************* |
| * Copyright (c) 2011 Obeo. |
| * 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: |
| * Obeo - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.mylyn.docs.intent.client.ui.editor; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.reconciler.DirtyRegion; |
| import org.eclipse.jface.text.reconciler.IReconcilingStrategy; |
| import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; |
| import org.eclipse.jface.text.source.Annotation; |
| import org.eclipse.jface.text.source.projection.ProjectionAnnotation; |
| import org.eclipse.mylyn.docs.intent.client.ui.IntentEditorActivator; |
| import org.eclipse.mylyn.docs.intent.client.ui.editor.scanner.IntentPartitionScanner; |
| import org.eclipse.mylyn.docs.intent.client.ui.preferences.IntentPreferenceConstants; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * This reconciling strategy will allow us to enable folding support in the Intent editor. |
| * |
| * @author <a href="mailto:william.piers@obeo.fr">William Piers</a> |
| */ |
| public final class IntentReconcilingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension { |
| |
| /** |
| * This will hold the list of all annotations that have been added since the last reconciling. |
| */ |
| private final Map<Annotation, Position> addedAnnotations = new HashMap<Annotation, Position>(); |
| |
| /** Current known positions of foldable block. */ |
| private final Map<Annotation, Position> currentAnnotations = new HashMap<Annotation, Position>(); |
| |
| /** |
| * This will hold the list of all annotations that have been removed since the last reconciling. |
| */ |
| private final List<Annotation> deletedAnnotations = new ArrayList<Annotation>(); |
| |
| /** Editor this provides folding support to. */ |
| private final IntentEditor editor; |
| |
| /** |
| * This will hold the list of all annotations that have been modified since the last reconciling. |
| */ |
| private final Map<Annotation, Position> modifiedAnnotations = new HashMap<Annotation, Position>(); |
| |
| /** The document we'll seek foldable blocks in. */ |
| private IDocument document; |
| |
| /** Current offset. */ |
| private int offset; |
| |
| /** |
| * Pair matcher used to reconcile document. |
| */ |
| private IntentPairMatcher pairMatcher = new IntentPairMatcher(); |
| |
| /** |
| * Instantiates the reconciling strategy for a given editor. |
| * |
| * @param editor |
| * Editor which we seek to provide folding support for. |
| */ |
| public IntentReconcilingStrategy(IntentEditor editor) { |
| this.editor = editor; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#initialReconcile() |
| */ |
| public void initialReconcile() { |
| offset = 0; |
| computePositions(); |
| updateFoldingStructure(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.reconciler.DirtyRegion, |
| * org.eclipse.jface.text.IRegion) |
| */ |
| public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { |
| reconcile(subRegion); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#reconcile(org.eclipse.jface.text.IRegion) |
| */ |
| public void reconcile(IRegion partition) { |
| offset = partition.getOffset(); |
| computePositions(); |
| updateFoldingStructure(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.jface.text.reconciler.IReconcilingStrategy#setDocument(org.eclipse.jface.text.IDocument) |
| */ |
| public void setDocument(IDocument document) { |
| this.document = document; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension#setProgressMonitor(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public void setProgressMonitor(IProgressMonitor monitor) { |
| // none |
| } |
| |
| /** |
| * This will compute the current block positions. The offset at which computations start is determined by |
| * {@link #offset}. |
| */ |
| private void computePositions() { |
| deletedAnnotations.clear(); |
| modifiedAnnotations.clear(); |
| addedAnnotations.clear(); |
| deletedAnnotations.addAll(currentAnnotations.keySet()); |
| |
| for (Map.Entry<Annotation, Position> entry : currentAnnotations.entrySet()) { |
| final Position position = entry.getValue(); |
| if (position.getOffset() + position.getLength() < offset) { |
| deletedAnnotations.remove(entry.getKey()); |
| } |
| } |
| try { |
| boolean eof = seekBlockStart(); |
| int startOffset = offset; |
| while (!eof) { |
| offset++; |
| // Case 1: Structural Content folding |
| if (document.getLineOfOffset(startOffset) > 0 |
| && document.getContentType(startOffset).equals( |
| IntentPartitionScanner.INTENT_STRUCTURAL_CONTENT)) { |
| IRegion match = pairMatcher.match(document, offset); |
| if (match != null) { |
| int endOffset = match.getOffset() + match.getLength() + 1; |
| if (document.getNumberOfLines(startOffset, endOffset - startOffset) > 2) { |
| createOrUpdateAnnotation(startOffset, endOffset - startOffset, false); |
| } |
| } |
| } else if (document.getLineOfOffset(startOffset) > 0 |
| && document.getContentType(startOffset).equals( |
| IntentPartitionScanner.INTENT_MODELINGUNIT)) { |
| // Case 2: Modeling Unit folding |
| // Search for modeling unit end |
| String documentZone = document.get().substring(startOffset); |
| int endOffset = documentZone.indexOf("M@") + 2; |
| if (endOffset > -1) { |
| createOrUpdateAnnotation(startOffset - 1, endOffset, |
| shouldCollapseModelingUnitByDefault()); |
| } |
| } |
| eof = seekBlockStart(); |
| startOffset = offset; |
| } |
| } catch (BadLocationException e) { |
| // Nothing to do |
| } |
| for (Annotation deleted : deletedAnnotations) { |
| currentAnnotations.remove(deleted); |
| } |
| } |
| |
| /** |
| * Eats chars away till we find a start char. |
| * |
| * @return <code>true</code> if the end of file has been reached. <code>false</code> otherwise. |
| * @throws BadLocationException |
| * Thrown if we try and get an out of range character. Should not happen. |
| */ |
| private boolean seekBlockStart() throws BadLocationException { |
| char next = document.getChar(offset); |
| char previous = ' '; |
| boolean eof = offset + 1 >= document.getLength(); |
| boolean foundModelingUnit = false; |
| while (!eof && next != '{' && !foundModelingUnit) { |
| offset++; |
| previous = next; |
| next = document.getChar(offset); |
| eof = offset + 1 == document.getLength(); |
| foundModelingUnit = next == 'M' && previous == '@'; |
| } |
| return eof; |
| } |
| |
| /** |
| * This will update lists {@link #deletedAnnotations}, {@link #addedAnnotations} and |
| * {@link #modifiedAnnotations} according to the given values. |
| * |
| * @param newOffset |
| * Offset of the text we want the annotation updated of. |
| * @param newLength |
| * Length of the text we want the annotation updated of. |
| * @param initiallyCollapsed |
| * Indicates that the created annotation should be folded from start. |
| * @throws BadLocationException |
| * Thrown if we try and get an out of range character. Should not happen. |
| */ |
| private void createOrUpdateAnnotation(final int newOffset, final int newLength, boolean initiallyCollapsed) |
| throws BadLocationException { |
| boolean createAnnotation = true; |
| final Map<Annotation, Position> copy = new HashMap<Annotation, Position>(currentAnnotations); |
| final String text = document.get(newOffset, newLength); |
| for (Iterator<Entry<Annotation, Position>> iterator = copy.entrySet().iterator(); iterator.hasNext();) { |
| Entry<Annotation, Position> entry = iterator.next(); |
| // added checking to avoid same text elements to bo ignored |
| if (entry.getKey().getText().equals(text) && entry.getValue().getOffset() == newOffset) { |
| createAnnotation = false; |
| final Position oldPosition = entry.getValue(); |
| if (oldPosition.getOffset() != newOffset || oldPosition.getLength() != newLength) { |
| final Position newPosition = new Position(newOffset, newLength); |
| modifiedAnnotations.put(entry.getKey(), newPosition); |
| currentAnnotations.put(entry.getKey(), newPosition); |
| } |
| deletedAnnotations.remove(entry.getKey()); |
| break; |
| } |
| } |
| if (createAnnotation) { |
| Annotation annotation = null; |
| annotation = new ProjectionAnnotation(initiallyCollapsed); |
| annotation.setText(text); |
| final Position position = new Position(newOffset, newLength); |
| currentAnnotations.put(annotation, position); |
| addedAnnotations.put(annotation, position); |
| } |
| } |
| |
| /** |
| * Updates the editor's folding structure. |
| */ |
| private void updateFoldingStructure() { |
| Display.getDefault().asyncExec(new Runnable() { |
| public void run() { |
| editor.updateFoldingStructure(addedAnnotations, deletedAnnotations, modifiedAnnotations); |
| } |
| }); |
| } |
| |
| /** |
| * Indicates whether Modeling units should be initially collapsed in the Intent editor. |
| * |
| * @return true if Modeling units should be initially collapsed in the Intent editor, false otherwise |
| */ |
| private boolean shouldCollapseModelingUnitByDefault() { |
| // We should collapse modeling units if |
| |
| return |
| // - preference say so |
| IntentEditorActivator.getDefault().getPreferenceStore() |
| .getBoolean(IntentPreferenceConstants.COLLAPSE_MODELING_UNITS) |
| && |
| // - and this is editor opening |
| !editor.isInitialFoldingStructureComplete(); |
| } |
| |
| } |