diff options
author | angelozerr | 2018-08-01 15:35:42 +0000 |
---|---|---|
committer | Mickael Istria | 2018-08-03 07:15:45 +0000 |
commit | fe1f173989deb9f16974d0c78d55f68c04a07462 (patch) | |
tree | d75e3894334ac161536142c44bf560273a6d37e5 | |
parent | 839c84221733f9fc9c6adc7a0a23d4132d79b92a (diff) | |
download | eclipse.platform.text-fe1f173989deb9f16974d0c78d55f68c04a07462.tar.gz eclipse.platform.text-fe1f173989deb9f16974d0c78d55f68c04a07462.tar.xz eclipse.platform.text-fe1f173989deb9f16974d0c78d55f68c04a07462.zip |
Bug 520659 - [generic editor] Default Code folding for generic editorI20180803-2000
should use IndentFoldingStrategy
Change-Id: If023933536584f07960fcb6b99c6dedcf6f6be1c
Signed-off-by: angelozerr <angelo.zerr@gmail.com>
13 files changed, 1154 insertions, 54 deletions
diff --git a/org.eclipse.ui.genericeditor.examples/plugin.xml b/org.eclipse.ui.genericeditor.examples/plugin.xml index 48f28ce0bd6..28f8ba11beb 100644 --- a/org.eclipse.ui.genericeditor.examples/plugin.xml +++ b/org.eclipse.ui.genericeditor.examples/plugin.xml @@ -47,11 +47,14 @@ </presentationReconciler> </extension> <extension - point="org.eclipse.ui.genericeditor.reconcilers"> - <reconciler + point="org.eclipse.ui.genericeditor.foldingReconcilers"> + <foldingReconciler class="org.eclipse.ui.genericeditor.examples.dotproject.FoldingReconciler" contentType="org.eclipse.ui.genericeditor.examples.dotproject"> - </reconciler> + </foldingReconciler> + </extension> + <extension + point="org.eclipse.ui.genericeditor.reconcilers"> <reconciler class="org.eclipse.ui.genericeditor.examples.dotproject.BracketMatchingReconciler" contentType="org.eclipse.ui.genericeditor.examples.dotproject"> diff --git a/org.eclipse.ui.genericeditor.tests/plugin.xml b/org.eclipse.ui.genericeditor.tests/plugin.xml index 1d81989bb2a..a65580a0934 100644 --- a/org.eclipse.ui.genericeditor.tests/plugin.xml +++ b/org.eclipse.ui.genericeditor.tests/plugin.xml @@ -235,6 +235,23 @@ </highlightReconciler> </extension> <extension + point="org.eclipse.ui.genericeditor.foldingReconcilers"> + <foldingReconciler + class="org.eclipse.ui.genericeditor.tests.contributions.FoldingReconciler" + contentType="org.eclipse.ui.genericeditor.tests.content-type-bar"> + </foldingReconciler> + <foldingReconciler + class="org.eclipse.ui.genericeditor.tests.contributions.FoldingReconciler" + contentType="org.eclipse.ui.genericeditor.tests.enabled-when-content-type"> + <enabledWhen> + <test + forcePluginActivation="true" + property="org.eclipse.ui.genericeditor.tests.contributions.enabled"> + </test> + </enabledWhen> + </foldingReconciler> + </extension> + <extension point="org.eclipse.core.expressions.propertyTesters"> <propertyTester class="org.eclipse.ui.genericeditor.tests.contributions.EnabledPropertyTester" diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/FoldingTest.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/FoldingTest.java new file mode 100644 index 00000000000..d8d66bf8eb2 --- /dev/null +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/FoldingTest.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2018 Angelo ZERR. + * 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: + * Angelo Zerr <angelo.zerr@gmail.com> - [generic editor] Default Code folding for generic editor should use IndentFoldingStrategy - Bug 520659 + */ +package org.eclipse.ui.genericeditor.tests; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import org.eclipse.swt.widgets.Display; + +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.projection.ProjectionAnnotation; +import org.eclipse.jface.text.source.projection.ProjectionViewer; +import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.genericeditor.tests.contributions.EnabledPropertyTester; + +public class FoldingTest extends AbstratGenericEditorTest { + + @Override + protected void createAndOpenFile() throws Exception { + //leave editor creation to individual tests + } + + @Test + public void testDefaultIndentFoldingOneFold() throws Exception { + createAndOpenFile("bar.xml", "<a>\n b</a>"); + checkFolding(pos(0, 10)); + } + + @Test + public void testDefaultIndentFoldingTwoFold() throws Exception { + createAndOpenFile("bar.xml", "<a>\n <b>\n c\n </b>\n</a>"); + checkFolding(pos(0, 19), pos(4, 9)); + } + + @Test + public void testCustomFoldingReconciler() throws Exception { + createAndOpenFile("bar.txt", "<a>\n <b>\n c\n </b>\n</a>\n"); + checkFolding(pos(0, 24), pos(5, 14)); + } + + @Test + public void testEnabledWhenCustomFoldingReconciler() throws Exception { + EnabledPropertyTester.setEnabled(true); + createAndOpenFile("enabledWhen.txt", "<a>\n <b>\n c\n </b>\n</a>\n"); + checkFolding(pos(0, 24), pos(5, 14)); + cleanFileAndEditor(); + + EnabledPropertyTester.setEnabled(false); + createAndOpenFile("enabledWhen.txt", "<a>\n <b>\n c\n </b>\n</a>\n"); + checkFolding(); + } + + private static Position pos(int offset, int length) { + return new Position(offset, length); + } + + private void checkFolding(Position... expectedPositions) { + if (expectedPositions == null) { + expectedPositions= new Position[0]; + } + waitForAnnotations(expectedPositions.length); + List<Annotation> folderAnnotations= getAnnotationsFromAnnotationModel(); + Assert.assertEquals(expectedPositions.length, folderAnnotations.size()); + List<Position> actualPositions= new ArrayList<>(expectedPositions.length); + for (int i= 0; i < expectedPositions.length; i++) { + Annotation folderAnnotation= folderAnnotations.get(i); + Position actualPosition= getProjectionAnnotationModel().getPosition(folderAnnotation); + actualPositions.add(actualPosition); + } + // Sort actual positions by offset + Collections.sort(actualPositions, (p1, p2) -> p1.offset - p2.offset); + Assert.assertArrayEquals(expectedPositions, actualPositions.toArray()); + } + + private IAnnotationModel getProjectionAnnotationModel() { + ProjectionViewer dp= (ProjectionViewer) editor.getAdapter(ITextViewer.class); + IAnnotationModel am= dp.getProjectionAnnotationModel(); + return am; + } + + private void waitForAnnotations(int count) { + new DisplayHelper() { + @Override + protected boolean condition() { + return getAnnotationsFromAnnotationModel().size() == count; + } + }.waitForCondition(Display.getDefault(), 2000); + } + + private List<Annotation> getAnnotationsFromAnnotationModel() { + List<Annotation> annotationList= new ArrayList<>(); + Iterator<Annotation> annotationIterator= getProjectionAnnotationModel().getAnnotationIterator(); + while (annotationIterator.hasNext()) { + Annotation ann= annotationIterator.next(); + if (ann.getType().equals(ProjectionAnnotation.TYPE)) { + annotationList.add(ann); + } + } + return annotationList; + } +} diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java index ee4c1940719..6ab16ce9db0 100644 --- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java @@ -22,6 +22,7 @@ import org.junit.runners.Suite.SuiteClasses; StylingTest.class, HoverTest.class, EditorTest.class, + FoldingTest.class, AutoEditTest.class, ReconcilerTest.class, HighlightTest.class diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingReconciler.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingReconciler.java new file mode 100644 index 00000000000..7a13de4b76e --- /dev/null +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingReconciler.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2017 Red Hat Inc. 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: + * Lucas Bullen (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.ui.genericeditor.tests.contributions; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.Reconciler; +import org.eclipse.jface.text.source.projection.ProjectionViewer; + +public class FoldingReconciler extends Reconciler { + + private FoldingStrategy fStrategy; + + public FoldingReconciler() { + fStrategy = new FoldingStrategy(); + this.setReconcilingStrategy(fStrategy, IDocument.DEFAULT_CONTENT_TYPE); + } + + @Override + public void install(ITextViewer textViewer) { + super.install(textViewer); + ProjectionViewer pViewer =(ProjectionViewer)textViewer; + fStrategy.setProjectionViewer(pViewer); + } +}
\ No newline at end of file diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingStrategy.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingStrategy.java new file mode 100644 index 00000000000..aeae3176324 --- /dev/null +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingStrategy.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2017 Red Hat Inc. 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: + * Lucas Bullen (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.ui.genericeditor.tests.contributions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.jface.text.source.projection.ProjectionViewer; + +public class FoldingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension { + + private IDocument document; + private String oldDocument; + private ProjectionViewer projectionViewer; + private List<Annotation> oldAnnotations = new ArrayList<>(); + private List<Position> oldPositions = new ArrayList<>(); + + @Override + public void setDocument(IDocument document) { + this.document = document; + } + + public void setProjectionViewer(ProjectionViewer projectionViewer) { + this.projectionViewer = projectionViewer; + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + initialReconcile(); + } + + @Override + public void reconcile(IRegion partition) { + initialReconcile(); + } + + @Override + public void initialReconcile() { + if(document.get().equals(oldDocument)) return; + oldDocument = document.get(); + + List<Position> positions = getNewPositionsOfAnnotations(); + + List<Position> positionsToRemove = new ArrayList<>(); + List<Annotation> annotationToRemove = new ArrayList<>(); + + for (Position position : oldPositions) { + if(!positions.contains(position)) { + projectionViewer.getProjectionAnnotationModel().removeAnnotation(oldAnnotations.get(oldPositions.indexOf(position))); + positionsToRemove.add(position); + annotationToRemove.add(oldAnnotations.get(oldPositions.indexOf(position))); + }else { + positions.remove(position); + } + } + oldPositions.removeAll(positionsToRemove); + oldAnnotations.removeAll(annotationToRemove); + + for (Position position : positions) { + Annotation annotation = new ProjectionAnnotation(); + projectionViewer.getProjectionAnnotationModel().addAnnotation(annotation, position); + oldPositions.add(position); + oldAnnotations.add(annotation); + } + } + + private static enum SearchingFor { + START_OF_TAG, START_OF_WORD, END_OF_WORD, END_OF_LINE + } + + private List<Position> getNewPositionsOfAnnotations(){ + List<Position> positions = new ArrayList<>(); + Map<String, Integer> startOfAnnotation = new HashMap<>(); + SearchingFor searchingFor = SearchingFor.START_OF_TAG; + + int characters = document.getLength(); + int currentCharIndex = 0; + + int wordStartIndex = 0; + int sectionStartIndex = 0; + String word = ""; + + try { + while (currentCharIndex < characters) { + char currentChar = document.getChar(currentCharIndex); + switch (searchingFor) { + case START_OF_TAG: + if(currentChar == '<') { + char nextChar = document.getChar(currentCharIndex+1); + if(nextChar != '?') { + sectionStartIndex = currentCharIndex; + searchingFor = SearchingFor.START_OF_WORD; + } + } + break; + case START_OF_WORD: + if(Character.isLetter(currentChar)) { + wordStartIndex = currentCharIndex; + searchingFor = SearchingFor.END_OF_WORD; + } + break; + case END_OF_WORD: + if(!Character.isLetter(currentChar)) { + word = document.get(wordStartIndex, currentCharIndex - wordStartIndex); + if(startOfAnnotation.containsKey(word)) { + searchingFor = SearchingFor.END_OF_LINE; + }else { + startOfAnnotation.put(word, sectionStartIndex); + searchingFor = SearchingFor.START_OF_TAG; + } + } + break; + case END_OF_LINE: + if(currentChar == '\n') { + int start = startOfAnnotation.get(word); + if(document.getLineOfOffset(start) != document.getLineOfOffset(currentCharIndex)) { + positions.add(new Position(start,currentCharIndex + 1 - start)); + } + startOfAnnotation.remove(word); + searchingFor = SearchingFor.START_OF_TAG; + } + break; + } + currentCharIndex++; + } + } catch (BadLocationException e) { + // skip the remainder of file due to error + } + return positions; + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + // no progress monitor used + } + +}
\ No newline at end of file diff --git a/org.eclipse.ui.genericeditor/plugin.properties b/org.eclipse.ui.genericeditor/plugin.properties index 725ae4c0484..492dde2b66a 100644 --- a/org.eclipse.ui.genericeditor/plugin.properties +++ b/org.eclipse.ui.genericeditor/plugin.properties @@ -18,6 +18,7 @@ ExtPoint.hoverProviders= Hover Providers ExtPoint.contentAssistProcessors=Content Assist Providers ExtPoint.autoEditStrategies=Auto Edit Strategies ExtPoint.highlightReconcilers=Highlight Reconcilers +ExtPoint.foldingReconcilers=Folding Reconcilers ExtPoint.hyperlinkDetectorTarget=Generic Text Editor openDeclarationCommand_name=Open Declaration context_name=in Generic Code Editor diff --git a/org.eclipse.ui.genericeditor/plugin.xml b/org.eclipse.ui.genericeditor/plugin.xml index 78ddce65442..3840a7bd319 100644 --- a/org.eclipse.ui.genericeditor/plugin.xml +++ b/org.eclipse.ui.genericeditor/plugin.xml @@ -20,6 +20,7 @@ <extension-point id="hoverProviders" name="%ExtPoint.hoverProviders" schema="schema/hoverProviders.exsd"/> <extension-point id="autoEditStrategies" name="%ExtPoint.autoEditStrategies" schema="schema/autoEditStrategies.exsd"/> <extension-point id="highlightReconcilers" name="%ExtPoint.highlightReconcilers" schema="schema/highlightReconcilers.exsd"/> + <extension-point id="foldingReconcilers" name="%ExtPoint.foldingReconcilers" schema="schema/foldingReconcilers.exsd"/> <extension point="org.eclipse.ui.editors"> <editor diff --git a/org.eclipse.ui.genericeditor/schema/foldingReconcilers.exsd b/org.eclipse.ui.genericeditor/schema/foldingReconcilers.exsd new file mode 100644 index 00000000000..f23cc301e7d --- /dev/null +++ b/org.eclipse.ui.genericeditor/schema/foldingReconcilers.exsd @@ -0,0 +1,155 @@ +<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.ui.genericeditor" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appinfo>
+ <meta.schema plugin="org.eclipse.ui.genericeditor" id="foldingReconcilers" name="Folding reconcilers"/>
+ </appinfo>
+ <documentation>
+ This extension point is used to contribute folding reconcilers for controlling the folding on a file with a given content type.
+ </documentation>
+ </annotation>
+
+ <include schemaLocation="schema://org.eclipse.core.expressions/schema/expressionLanguage.exsd"/>
+
+ <element name="extension">
+ <annotation>
+ <appinfo>
+ <meta.element />
+ </appinfo>
+ </annotation>
+ <complexType>
+ <sequence minOccurs="1" maxOccurs="unbounded">
+ <element ref="foldingReconciler"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+ a fully qualified identifier of the target extension point
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+ an optional identifier of the extension instance
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+ an optional name of the extension instance
+ </documentation>
+ <appinfo>
+ <meta.attribute translatable="true"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="foldingReconciler">
+ <complexType>
+ <sequence>
+ <element ref="enabledWhen" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ <attribute name="class" type="string" use="required">
+ <annotation>
+ <documentation>
+ The fully qualified class name implementing the interface <code>org.eclipse.jface.text.reconciler.IReconciler</code>
+ To manipulate folding, the implementation reconciler needs to use ProjectionAnnotation and viewer.getProjectionModel(). You can find a sample in
+ org.eclipse.ui.internal.genericeditor.folding.DefaultFoldingReconciler.
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="java" basedOn=":org.eclipse.jface.text.reconciler.IReconciler"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ <attribute name="contentType" type="string" use="required">
+ <annotation>
+ <documentation>
+ The target content-type for this extension. Content-types are defined as extension to the org.eclipse.core.contenttype.contentTypes extension point.
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="identifier" basedOn="org.eclipse.core.contenttype.contentTypes/content-type/@id"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="enabledWhen">
+ <annotation>
+ <documentation>
+ A core Expression that controls the enabled of the given folding reconciler. The viewer, editor, and editor input are registered in the evaluation context as variable:
+
+ * <with variable="viewer"/> : use it if your expression requires the viewer.
+ * <with variable="editor"/> : use it if your expression requires the editor.
+ * <with variable="editorInput"/> : use it if your expression requires the editor input.
+ </documentation>
+ </annotation>
+ <complexType>
+ <choice minOccurs="0" maxOccurs="1">
+ <element ref="not"/>
+ <element ref="or"/>
+ <element ref="and"/>
+ <element ref="instanceof"/>
+ <element ref="test"/>
+ <element ref="systemTest"/>
+ <element ref="equals"/>
+ <element ref="count"/>
+ <element ref="with"/>
+ <element ref="resolve"/>
+ <element ref="adapt"/>
+ <element ref="iterate"/>
+ <element ref="reference"/>
+ </choice>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="since"/>
+ </appinfo>
+ <documentation>
+ 1.1
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="examples"/>
+ </appinfo>
+ <documentation>
+ Below is an example of how to use the Folding Reconciler extension point:
+<pre>
+<extension point="org.eclipse.ui.genericeditor.foldingReconcilers">
+ <foldingReconciler
+ class="org.eclipse.ui.genericeditor.examples.TargetDefinitionFoldingReconciler"
+ contentType="org.eclipse.pde.targetFile">
+ <enabledWhen>
+ <with variable="editor">
+ <test property="org.eclipse.ui.genericeditor.examples.TargetDefinitionPropertyTester">
+ </test>
+ </with>
+ </enabledWhen>
+ </foldingReconciler>
+</extension>
+</pre>
+ </documentation>
+ </annotation>
+
+
+
+ <annotation>
+ <appinfo>
+ <meta.section type="copyright"/>
+ </appinfo>
+ <documentation>
+ Copyright (c) 2017 Red Hat Inc. 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 <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>
+ </documentation>
+ </annotation>
+
+</schema>
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java index 1d149b3b38f..44f2832655b 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java @@ -10,6 +10,7 @@ * Lucas Bullen (Red Hat Inc.) - Bug 508829 custom reconciler support * - Bug 521382 default highlight reconciler * Simon Scholz <simon.scholz@vogella.com> - Bug 527830 + * Angelo Zerr <angelo.zerr@gmail.com> - [generic editor] Default Code folding for generic editor should use IndentFoldingStrategy - Bug 520659 *******************************************************************************/ package org.eclipse.ui.internal.genericeditor; @@ -44,20 +45,22 @@ import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; import org.eclipse.ui.internal.editors.text.EditorsPlugin; +import org.eclipse.ui.internal.genericeditor.folding.DefaultFoldingReconciler; import org.eclipse.ui.internal.genericeditor.hover.CompositeTextHover; import org.eclipse.ui.internal.genericeditor.markers.MarkerResoltionQuickAssistProcessor; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.spelling.SpellingCorrectionProcessor; /** - * The configuration of the {@link ExtensionBasedTextEditor}. It registers the proxy composite - * for hover, completion, syntax highlighting, and then those proxy take care of resolving to - * the right extensions on-demand. + * The configuration of the {@link ExtensionBasedTextEditor}. It registers the + * proxy composite for hover, completion, syntax highlighting, and then those + * proxy take care of resolving to the right extensions on-demand. * * @since 1.0 */ @SuppressWarnings("restriction") -public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewerConfiguration implements IDocumentPartitioningListener { +public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewerConfiguration + implements IDocumentPartitioningListener { private ITextEditor editor; private Set<IContentType> contentTypes; @@ -68,7 +71,7 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe /** * - * @param editor the editor we're creating. + * @param editor the editor we're creating. * @param preferenceStore the preference store. */ public ExtensionBasedTextViewerConfiguration(ITextEditor editor, IPreferenceStore preferenceStore) { @@ -84,7 +87,8 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe private Set<IContentType> getContentTypes() { if (this.contentTypes == null) { this.contentTypes = new LinkedHashSet<>(); - Queue<IContentType> types = new LinkedList<>(Arrays.asList(Platform.getContentTypeManager().findContentTypesFor(editor.getEditorInput().getName()))); + Queue<IContentType> types = new LinkedList<>(Arrays + .asList(Platform.getContentTypeManager().findContentTypesFor(editor.getEditorInput().getName()))); while (!types.isEmpty()) { IContentType type = types.poll(); this.contentTypes.add(type); @@ -99,7 +103,8 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { - List<ITextHover> hovers = GenericEditorPlugin.getDefault().getHoverRegistry().getAvailableHovers(sourceViewer, editor, getContentTypes()); + List<ITextHover> hovers = GenericEditorPlugin.getDefault().getHoverRegistry().getAvailableHovers(sourceViewer, + editor, getContentTypes()); if (hovers == null || hovers.isEmpty()) { return null; } else if (hovers.size() == 1) { @@ -111,8 +116,8 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { - ContentAssistProcessorRegistry registry= GenericEditorPlugin.getDefault().getContentAssistProcessorRegistry(); - contentAssistant= new ContentAssistant(true); + ContentAssistProcessorRegistry registry = GenericEditorPlugin.getDefault().getContentAssistProcessorRegistry(); + contentAssistant = new ContentAssistant(true); contentAssistant.setContextInformationPopupOrientation(ContentAssistant.CONTEXT_INFO_BELOW); contentAssistant.setProposalPopupOrientation(ContentAssistant.PROPOSAL_REMOVE); contentAssistant.setAutoActivationDelay(0); @@ -140,7 +145,8 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { PresentationReconcilerRegistry registry = GenericEditorPlugin.getDefault().getPresentationReconcilerRegistry(); - List<IPresentationReconciler> reconciliers = registry.getPresentationReconcilers(sourceViewer, editor, getContentTypes()); + List<IPresentationReconciler> reconciliers = registry.getPresentationReconcilers(sourceViewer, editor, + getContentTypes()); if (!reconciliers.isEmpty()) { return reconciliers.get(0); } @@ -178,30 +184,38 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) { QuickAssistAssistant quickAssistAssistant = new QuickAssistAssistant(); - CompositeQuickAssistProcessor processor = new CompositeQuickAssistProcessor(Arrays.asList( - new MarkerResoltionQuickAssistProcessor(), - new SpellingCorrectionProcessor() - )); + CompositeQuickAssistProcessor processor = new CompositeQuickAssistProcessor( + Arrays.asList(new MarkerResoltionQuickAssistProcessor(), new SpellingCorrectionProcessor())); quickAssistAssistant.setQuickAssistProcessor(processor); - quickAssistAssistant.setRestoreCompletionProposalSize(EditorsPlugin.getDefault().getDialogSettingsSection("quick_assist_proposal_size")); //$NON-NLS-1$ - quickAssistAssistant.setInformationControlCreator(parent -> new DefaultInformationControl(parent, EditorsPlugin.getAdditionalInfoAffordanceString())); + quickAssistAssistant.setRestoreCompletionProposalSize( + EditorsPlugin.getDefault().getDialogSettingsSection("quick_assist_proposal_size")); //$NON-NLS-1$ + quickAssistAssistant.setInformationControlCreator( + parent -> new DefaultInformationControl(parent, EditorsPlugin.getAdditionalInfoAffordanceString())); return quickAssistAssistant; } @Override public IReconciler getReconciler(ISourceViewer sourceViewer) { ReconcilerRegistry registry = GenericEditorPlugin.getDefault().getReconcilerRegistry(); - List<IReconciler> reconciliers = registry.getReconcilers(sourceViewer, editor, getContentTypes()); - List<IReconciler> highlightReconciliers = registry.getHighlightReconcilers(sourceViewer, editor, getContentTypes()); - - if(!highlightReconciliers.isEmpty()) { - reconciliers.addAll(highlightReconciliers); - }else { - reconciliers.add(new DefaultWordHighlightReconciler()); + List<IReconciler> reconcilers = registry.getReconcilers(sourceViewer, editor, getContentTypes()); + // Fill with highlight reconcilers + List<IReconciler> highlightReconcilers = registry.getHighlightReconcilers(sourceViewer, editor, + getContentTypes()); + if (!highlightReconcilers.isEmpty()) { + reconcilers.addAll(highlightReconcilers); + } else { + reconcilers.add(new DefaultWordHighlightReconciler()); + } + // Fill with folding reconcilers + List<IReconciler> foldingReconcilers = registry.getFoldingReconcilers(sourceViewer, editor, getContentTypes()); + if (!foldingReconcilers.isEmpty()) { + reconcilers.addAll(foldingReconcilers); + } else { + reconcilers.add(new DefaultFoldingReconciler()); } - if (!reconciliers.isEmpty()) { - return new CompositeReconciler(reconciliers); + if (!reconcilers.isEmpty()) { + return new CompositeReconciler(reconcilers); } return null; } @@ -209,7 +223,8 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer, String contentType) { AutoEditStrategyRegistry registry = GenericEditorPlugin.getDefault().getAutoEditStrategyRegistry(); - List<IAutoEditStrategy> editStrategies = registry.getAutoEditStrategies(sourceViewer, editor, getContentTypes()); + List<IAutoEditStrategy> editStrategies = registry.getAutoEditStrategies(sourceViewer, editor, + getContentTypes()); if (!editStrategies.isEmpty()) { return editStrategies.toArray(new IAutoEditStrategy[editStrategies.size()]); } @@ -218,7 +233,7 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override protected Map<String, IAdaptable> getHyperlinkDetectorTargets(ISourceViewer sourceViewer) { - Map<String, IAdaptable> targets= super.getHyperlinkDetectorTargets(sourceViewer); + Map<String, IAdaptable> targets = super.getHyperlinkDetectorTargets(sourceViewer); targets.put("org.eclipse.ui.genericeditor.GenericEditor", editor); //$NON-NLS-1$ return targets; } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java index 74e8183d75d..148ef13c725 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java @@ -27,9 +27,10 @@ import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.ui.texteditor.ITextEditor; /** - * A registry of reconciliers provided by extensions <code>org.eclipse.ui.genericeditor.reconcilers</code> - * and <code>org.eclipse.ui.genericeditor.foldingReconcilers</code>. - * Those extensions are specific to a given {@link IContentType}. + * A registry of reconciliers provided by extensions + * <code>org.eclipse.ui.genericeditor.reconcilers</code> and + * <code>org.eclipse.ui.genericeditor.foldingReconcilers</code>. Those + * extensions are specific to a given {@link IContentType}. * * @since 1.1 */ @@ -37,11 +38,14 @@ public class ReconcilerRegistry { private static final String EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".reconcilers"; //$NON-NLS-1$ private static final String HIGHLIGHT_EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".highlightReconcilers"; //$NON-NLS-1$ + private static final String FOLDING_EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".foldingReconcilers"; //$NON-NLS-1$ private Map<IConfigurationElement, GenericContentTypeRelatedExtension<IReconciler>> extensions = new HashMap<>(); private Map<IConfigurationElement, GenericContentTypeRelatedExtension<IReconciler>> highlightExtensions = new HashMap<>(); + private Map<IConfigurationElement, GenericContentTypeRelatedExtension<IReconciler>> foldingExtensions = new HashMap<>(); private boolean outOfSync = true; private boolean highlightOutOfSync = true; + private boolean foldingOutOfSync = true; /** * Creates the registry and binds it to the extension point. @@ -54,18 +58,24 @@ public class ReconcilerRegistry { Platform.getExtensionRegistry().addRegistryChangeListener(event -> { highlightOutOfSync = true; }, HIGHLIGHT_EXTENSION_POINT_ID); + + Platform.getExtensionRegistry().addRegistryChangeListener(event -> { + foldingOutOfSync = true; + }, FOLDING_EXTENSION_POINT_ID); } /** - * Get the contributed {@link IReconciliers}s that are relevant to hook on source viewer according - * to document content types. + * Get the contributed {@link IReconciliers}s that are relevant to hook on + * source viewer according to document content types. + * * @param sourceViewer the source viewer we're hooking completion to. - * @param editor the text editor + * @param editor the text editor * @param contentTypes the content types of the document we're editing. - * @return the list of {@link IReconciler} contributed for at least one of the content types, - * sorted by most generic content type to most specific. + * @return the list of {@link IReconciler} contributed for at least one of the + * content types, sorted by most generic content type to most specific. */ - public List<IReconciler> getReconcilers(ISourceViewer sourceViewer, ITextEditor editor, Set<IContentType> contentTypes) { + public List<IReconciler> getReconcilers(ISourceViewer sourceViewer, ITextEditor editor, + Set<IContentType> contentTypes) { if (this.outOfSync) { sync(); } @@ -73,21 +83,23 @@ public class ReconcilerRegistry { .filter(ext -> contentTypes.contains(ext.targetContentType)) .filter(ext -> ext.matches(sourceViewer, editor)) .sorted(new ContentTypeSpecializationComparator<IReconciler>().reversed()) - .map(GenericContentTypeRelatedExtension<IReconciler>::createDelegate) - .collect(Collectors.toList()); + .map(GenericContentTypeRelatedExtension<IReconciler>::createDelegate).collect(Collectors.toList()); return reconcilers; } /** - * Get the contributed highlight {@link IReconciliers}s that are relevant to hook on source viewer according - * to document content types. + * Get the contributed highlight {@link IReconciliers}s that are relevant to + * hook on source viewer according to document content types. + * * @param sourceViewer the source viewer we're hooking completion to. - * @param editor the text editor + * @param editor the text editor * @param contentTypes the content types of the document we're editing. - * @return the list of highlight {@link IReconciler}s contributed for at least one of the content types, - * sorted by most generic content type to most specific. + * @return the list of highlight {@link IReconciler}s contributed for at least + * one of the content types, sorted by most generic content type to most + * specific. */ - public List<IReconciler> getHighlightReconcilers(ISourceViewer sourceViewer, ITextEditor editor, Set<IContentType> contentTypes) { + public List<IReconciler> getHighlightReconcilers(ISourceViewer sourceViewer, ITextEditor editor, + Set<IContentType> contentTypes) { if (this.highlightOutOfSync) { syncHighlight(); } @@ -95,20 +107,45 @@ public class ReconcilerRegistry { .filter(ext -> contentTypes.contains(ext.targetContentType)) .filter(ext -> ext.matches(sourceViewer, editor)) .sorted(new ContentTypeSpecializationComparator<IReconciler>().reversed()) - .map(GenericContentTypeRelatedExtension<IReconciler>::createDelegate) - .collect(Collectors.toList()); + .map(GenericContentTypeRelatedExtension<IReconciler>::createDelegate).collect(Collectors.toList()); return highlightReconcilers; } + /** + * Get the contributed folding {@link IReconciliers}s that are relevant to hook + * on source viewer according to document content types. + * + * @param sourceViewer the source viewer we're hooking completion to. + * @param editor the text editor + * @param contentTypes the content types of the document we're editing. + * @return the list of folding {@link IReconciler}s contributed for at least one + * of the content types, sorted by most generic content type to most + * specific. + */ + public List<IReconciler> getFoldingReconcilers(ISourceViewer sourceViewer, ITextEditor editor, + Set<IContentType> contentTypes) { + if (this.foldingOutOfSync) { + syncFolding(); + } + List<IReconciler> foldingReconcilers = this.foldingExtensions.values().stream() + .filter(ext -> contentTypes.contains(ext.targetContentType)) + .filter(ext -> ext.matches(sourceViewer, editor)) + .sorted(new ContentTypeSpecializationComparator<IReconciler>().reversed()) + .map(GenericContentTypeRelatedExtension<IReconciler>::createDelegate).collect(Collectors.toList()); + return foldingReconcilers; + } + private void sync() { Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet()); - for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID)) { + for (IConfigurationElement extension : Platform.getExtensionRegistry() + .getConfigurationElementsFor(EXTENSION_POINT_ID)) { toRemoveExtensions.remove(extension); if (!this.extensions.containsKey(extension)) { try { this.extensions.put(extension, new GenericContentTypeRelatedExtension<IReconciler>(extension)); } catch (Exception ex) { - GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); + GenericEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); } } } @@ -120,13 +157,16 @@ public class ReconcilerRegistry { private void syncHighlight() { Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet()); - for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(HIGHLIGHT_EXTENSION_POINT_ID)) { + for (IConfigurationElement extension : Platform.getExtensionRegistry() + .getConfigurationElementsFor(HIGHLIGHT_EXTENSION_POINT_ID)) { toRemoveExtensions.remove(extension); if (!this.highlightExtensions.containsKey(extension)) { try { - this.highlightExtensions.put(extension, new GenericContentTypeRelatedExtension<IReconciler>(extension)); + this.highlightExtensions.put(extension, + new GenericContentTypeRelatedExtension<IReconciler>(extension)); } catch (Exception ex) { - GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); + GenericEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); } } } @@ -136,4 +176,24 @@ public class ReconcilerRegistry { this.highlightOutOfSync = false; } + private void syncFolding() { + Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet()); + for (IConfigurationElement extension : Platform.getExtensionRegistry() + .getConfigurationElementsFor(FOLDING_EXTENSION_POINT_ID)) { + toRemoveExtensions.remove(extension); + if (!this.foldingExtensions.containsKey(extension)) { + try { + this.foldingExtensions.put(extension, + new GenericContentTypeRelatedExtension<IReconciler>(extension)); + } catch (Exception ex) { + GenericEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); + } + } + } + for (IConfigurationElement toRemove : toRemoveExtensions) { + this.foldingExtensions.remove(toRemove); + } + this.foldingOutOfSync = false; + } } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/DefaultFoldingReconciler.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/DefaultFoldingReconciler.java new file mode 100644 index 00000000000..01ab3fb731c --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/DefaultFoldingReconciler.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2018 Angelo ZERR. + * 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: + * Angelo Zerr <angelo.zerr@gmail.com> - [generic editor] Default Code folding for generic editor should use IndentFoldingStrategy - Bug 520659 + */ +package org.eclipse.ui.internal.genericeditor.folding; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.AbstractReconciler; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.source.projection.ProjectionViewer; + +public class DefaultFoldingReconciler extends AbstractReconciler { + + private final IndentFoldingStrategy foldingStrategy; + + public DefaultFoldingReconciler() { + this.foldingStrategy = new IndentFoldingStrategy(); + } + + @Override + public void install(ITextViewer textViewer) { + super.install(textViewer); + ProjectionViewer viewer = (ProjectionViewer) textViewer; + foldingStrategy.setViewer(viewer); + } + + @Override + public void uninstall() { + super.uninstall(); + foldingStrategy.uninstall(); + } + + @Override + protected void process(DirtyRegion dirtyRegion) { + foldingStrategy.reconcile(dirtyRegion, null); + } + + @Override + protected void reconcilerDocumentChanged(IDocument newDocument) { + foldingStrategy.setDocument(newDocument); + } + + @Override + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + return foldingStrategy; + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + super.setProgressMonitor(monitor); + foldingStrategy.setProgressMonitor(monitor); + } + + @Override + protected void initialProcess() { + super.initialProcess(); + foldingStrategy.initialReconcile(); + } +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java new file mode 100644 index 00000000000..1fa3161216e --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java @@ -0,0 +1,471 @@ +/*******************************************************************************
+ * Copyright (c) 2009, 2017 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
+ * Angelo Zerr <angelo.zerr@gmail.com> - adapt code org.eclipse.wst.sse.ui.internal.projection.AbstractStructuredFoldingStrategy to support generic indent folding strategy.
+ * [generic editor] Default Code folding for generic editor should use IndentFoldingStrategy - Bug 520659
+ */
+package org.eclipse.ui.internal.genericeditor.folding;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+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.IProjectionListener;
+import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
+import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
+import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+
+/**
+ * Indent folding strategy to fold code by using indentation. The folding
+ * strategy must be associated with a viewer for it to function.
+ */
+public class IndentFoldingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension, IProjectionListener {
+
+ private IDocument document;
+ private ProjectionViewer viewer;
+ private ProjectionAnnotationModel projectionAnnotationModel;
+ private final String lineStartsWithKeyword;
+
+ public IndentFoldingStrategy() {
+ this(null);
+ }
+
+ public IndentFoldingStrategy(String lineStartsWithKeyword) {
+ this.lineStartsWithKeyword = lineStartsWithKeyword;
+ }
+
+ /**
+ * A FoldingAnnotation is a {@link ProjectionAnnotation} it is folding and
+ * overriding the paint method (in a hacky type way) to prevent one line folding
+ * annotations to be drawn.
+ */
+ protected class FoldingAnnotation extends ProjectionAnnotation {
+ private boolean visible; /* workaround for BUG85874 */
+
+ /**
+ * Creates a new FoldingAnnotation.
+ *
+ * @param isCollapsed true if this annotation should be collapsed, false
+ * otherwise
+ */
+ public FoldingAnnotation(boolean isCollapsed) {
+ super(isCollapsed);
+ visible = false;
+ }
+
+ /**
+ * Does not paint hidden annotations. Annotations are hidden when they only span
+ * one line.
+ *
+ * @see ProjectionAnnotation#paint(org.eclipse.swt.graphics.GC,
+ * org.eclipse.swt.widgets.Canvas, org.eclipse.swt.graphics.Rectangle)
+ */
+ @Override
+ public void paint(GC gc, Canvas canvas, Rectangle rectangle) {
+ /* workaround for BUG85874 */
+ /*
+ * only need to check annotations that are expanded because hidden annotations
+ * should never have been given the chance to collapse.
+ */
+ if (!isCollapsed()) {
+ // working with rectangle, so line height
+ FontMetrics metrics = gc.getFontMetrics();
+ if (metrics != null) {
+ // do not draw annotations that only span one line and
+ // mark them as not visible
+ if ((rectangle.height / metrics.getHeight()) <= 1) {
+ visible = false;
+ return;
+ }
+ }
+ }
+ visible = true;
+ super.paint(gc, canvas, rectangle);
+ }
+
+ @Override
+ public void markCollapsed() {
+ /* workaround for BUG85874 */
+ // do not mark collapsed if annotation is not visible
+ if (visible)
+ super.markCollapsed();
+ }
+ }
+
+ /**
+ * The folding strategy must be associated with a viewer for it to function
+ *
+ * @param viewer the viewer to associate this folding strategy with
+ */
+ public void setViewer(ProjectionViewer viewer) {
+ if (this.viewer != null) {
+ this.viewer.removeProjectionListener(this);
+ }
+ this.viewer = viewer;
+ this.viewer.addProjectionListener(this);
+ this.projectionAnnotationModel = this.viewer.getProjectionAnnotationModel();
+ }
+
+ public void uninstall() {
+ setDocument(null);
+
+ if (viewer != null) {
+ viewer.removeProjectionListener(this);
+ viewer = null;
+ }
+
+ projectionDisabled();
+ }
+
+ @Override
+ public void setDocument(IDocument document) {
+ this.document = document;
+ }
+
+ @Override
+ public void projectionDisabled() {
+ projectionAnnotationModel = null;
+ }
+
+ @Override
+ public void projectionEnabled() {
+ if (viewer != null) {
+ projectionAnnotationModel = viewer.getProjectionAnnotationModel();
+ }
+ }
+
+ private class LineIndent {
+ public int line;
+ public final int indent;
+
+ public LineIndent(int line, int indent) {
+ this.line = line;
+ this.indent = indent;
+ }
+ }
+
+ @Override
+ public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
+ if (projectionAnnotationModel != null) {
+
+ // these are what are passed off to the annotation model to
+ // actually create and maintain the annotations
+ List<Annotation> modifications = new ArrayList<Annotation>();
+ List<FoldingAnnotation> deletions = new ArrayList<FoldingAnnotation>();
+ List<FoldingAnnotation> existing = new ArrayList<FoldingAnnotation>();
+ Map<Annotation, Position> additions = new HashMap<Annotation, Position>();
+ boolean isInsert = dirtyRegion.getType().equals(DirtyRegion.INSERT);
+ boolean isRemove = dirtyRegion.getType().equals(DirtyRegion.REMOVE);
+
+ // find and mark all folding annotations with length 0 for deletion
+ markInvalidAnnotationsForDeletion(dirtyRegion, deletions, existing);
+
+ List<LineIndent> previousRegions = new ArrayList<LineIndent>();
+
+ int tabSize = 1;
+ int minimumRangeSize = 1;
+ try {
+
+ // Today we recompute annotation from the whole document each
+ // time.
+ // performance s good even with large document, but it should be
+ // better to loop for only DirtyRegion (and before/after)
+ // int offset = dirtyRegion.getOffset();
+ // int length = dirtyRegion.getLength();
+ // int startLine = 0; //document.getLineOfOffset(offset);
+ int endLine = document.getNumberOfLines() - 1; // startLine +
+ // document.getNumberOfLines(offset,
+ // length) - 1;
+
+ // sentinel, to make sure there's at least one entry
+ previousRegions.add(new LineIndent(endLine + 1, -1));
+
+ int lastLineWhichIsNotEmpty = 0;
+ int lineEmptyCount = 0;
+ Integer lastLineForKeyword = null;
+ int line = endLine;
+ for (line = endLine; line >= 0; line--) {
+ int lineOffset = document.getLineOffset(line);
+ String delim = document.getLineDelimiter(line);
+ int lineLength = document.getLineLength(line) - (delim != null ? delim.length() : 0);
+ String lineContent = document.get(lineOffset, lineLength);
+
+ LineState state = getLineState(lineContent, lastLineForKeyword);
+ switch (state) {
+ case StartWithKeyWord:
+ lineEmptyCount = 0;
+ lastLineWhichIsNotEmpty = line;
+ if (lastLineForKeyword == null) {
+ lastLineForKeyword = line;
+ }
+ break;
+ case EmptyLine:
+ lineEmptyCount++;
+ break;
+ default:
+ addAnnotationForKeyword(modifications, deletions, existing, additions,
+ line + 1 + lineEmptyCount, lastLineForKeyword);
+ lastLineForKeyword = null;
+ lineEmptyCount = 0;
+ lastLineWhichIsNotEmpty = line;
+ int indent = computeIndentLevel(lineContent, tabSize);
+ if (indent == -1) {
+ continue; // only whitespace
+ }
+
+ LineIndent previous = previousRegions.get(previousRegions.size() - 1);
+ if (previous.indent > indent) {
+ // discard all regions with larger indent
+ do {
+ previousRegions.remove(previousRegions.size() - 1);
+ previous = previousRegions.get(previousRegions.size() - 1);
+ } while (previous.indent > indent);
+
+ // new folding range
+ int endLineNumber = previous.line - 1;
+ if (endLineNumber - line >= minimumRangeSize) {
+ updateAnnotation(modifications, deletions, existing, additions, line, endLineNumber);
+ }
+ }
+ if (previous.indent == indent) {
+ previous.line = line;
+ } else { // previous.indent < indent
+ // new region with a bigger indent
+ previousRegions.add(new LineIndent(line, indent));
+ }
+ }
+ }
+ addAnnotationForKeyword(modifications, deletions, existing, additions, lastLineWhichIsNotEmpty,
+ lastLineForKeyword);
+ } catch (BadLocationException e) {
+ // should never done
+ e.printStackTrace();
+ }
+
+ // be sure projection has not been disabled
+ if (projectionAnnotationModel != null) {
+ if (existing.size() > 0) {
+ deletions.addAll(existing);
+ }
+ // send the calculated updates to the annotations to the
+ // annotation model
+ projectionAnnotationModel.modifyAnnotations(deletions.toArray(new Annotation[1]), additions,
+ modifications.toArray(new Annotation[0]));
+ }
+ }
+ }
+
+ private void addAnnotationForKeyword(List<Annotation> modifications, List<FoldingAnnotation> deletions,
+ List<FoldingAnnotation> existing, Map<Annotation, Position> additions, int startLine,
+ Integer lastLineForKeyword) throws BadLocationException {
+ if (lastLineForKeyword != null) {
+ updateAnnotation(modifications, deletions, existing, additions, startLine, lastLineForKeyword);
+ }
+ }
+
+ private enum LineState {
+ StartWithKeyWord, DontStartWithKeyWord, EmptyLine
+ }
+
+ /**
+ * Returns the line state for line which starts with a given keyword.
+ *
+ * @param lineContent line content.
+ * @param lastLineForKeyword last line for the given keyword.
+ * @return
+ */
+ private LineState getLineState(String lineContent, Integer lastLineForKeyword) {
+ if (lineStartsWithKeyword == null) {
+ // none keyword defined.
+ return LineState.DontStartWithKeyWord;
+ }
+ if (lineContent != null && lineContent.trim().startsWith(lineStartsWithKeyword)) {
+ // The line starts with the given keyword (ex: starts with "import")
+ return LineState.StartWithKeyWord;
+ }
+ if (lastLineForKeyword != null && (lineContent == null || lineContent.trim().length() == 0)) {
+ // a last line for keyword was defined, line is empty
+ return LineState.EmptyLine;
+ }
+ return LineState.DontStartWithKeyWord;
+ }
+
+ /**
+ * Compute indentation level of the given line by using the given tab size.
+ *
+ * @param line the line text.
+ * @param tabSize the tab size.
+ * @return the indentation level of the given line by using the given tab size.
+ */
+ private static int computeIndentLevel(String line, int tabSize) {
+ int i = 0;
+ int indent = 0;
+ while (i < line.length()) {
+ char ch = line.charAt(i);
+ if (ch == ' ') {
+ indent++;
+ } else if (ch == '\t') {
+ indent = indent - indent % tabSize + tabSize;
+ } else {
+ break;
+ }
+ i++;
+ }
+ if (i == line.length()) {
+ return -1; // line only consists of whitespace
+ }
+ return indent;
+ }
+
+ /**
+ * Given a {@link DirtyRegion} returns an {@link Iterator} of the already
+ * existing annotations in that region.
+ *
+ * @param dirtyRegion the {@link DirtyRegion} to check for existing annotations
+ * in
+ *
+ * @return an {@link Iterator} over the annotations in the given
+ * {@link DirtyRegion}. The iterator could have no annotations in it. Or
+ * <code>null</code> if projection has been disabled.
+ */
+ private Iterator<Annotation> getAnnotationIterator(DirtyRegion dirtyRegion) {
+ Iterator<Annotation> annoIter = null;
+ // be sure project has not been disabled
+ if (projectionAnnotationModel != null) {
+ // workaround for Platform Bug 299416
+ int offset = dirtyRegion.getOffset();
+ if (offset > 0) {
+ offset--;
+ }
+ annoIter = projectionAnnotationModel.getAnnotationIterator(0, document.getLength(), false, false);
+ }
+ return annoIter;
+ }
+
+ /**
+ * Update annotations.
+ *
+ * @param modifications the folding annotations to update.
+ * @param deletions the folding annotations to delete.
+ * @param existing the existing folding annotations.
+ * @param additions annoation to add
+ * @param line the line index
+ * @param endLineNumber the end line number
+ * @throws BadLocationException
+ */
+ private void updateAnnotation(List<Annotation> modifications, List<FoldingAnnotation> deletions,
+ List<FoldingAnnotation> existing, Map<Annotation, Position> additions, int line, Integer endLineNumber)
+ throws BadLocationException {
+ int startOffset = document.getLineOffset(line);
+ int endOffset = document.getLineOffset(endLineNumber) + document.getLineLength(endLineNumber);
+ Position newPos = new Position(startOffset, endOffset - startOffset);
+ if (existing.size() > 0) {
+ FoldingAnnotation existingAnnotation = existing.remove(existing.size() - 1);
+ updateAnnotations(existingAnnotation, newPos, modifications, deletions);
+ } else {
+ additions.put(new FoldingAnnotation(false), newPos);
+ }
+ }
+
+ /**
+ * Update annotations.
+ *
+ * @param existingAnnotation the existing annotations that need to be updated
+ * based on the given dirtied IndexRegion
+ * @param newPos the new position that caused the annotations need
+ * for updating and null otherwise.
+ * @param modifications the list of annotations to be modified
+ * @param deletions the list of annotations to be deleted
+ */
+ protected void updateAnnotations(Annotation existingAnnotation, Position newPos, List<Annotation> modifications,
+ List<FoldingAnnotation> deletions) {
+ if (existingAnnotation instanceof FoldingAnnotation) {
+ FoldingAnnotation foldingAnnotation = (FoldingAnnotation) existingAnnotation;
+
+ // if a new position can be calculated then update the position of
+ // the annotation,
+ // else the annotation needs to be deleted
+ if (newPos != null && newPos.length > 0 && projectionAnnotationModel != null) {
+ Position oldPos = projectionAnnotationModel.getPosition(foldingAnnotation);
+ // only update the position if we have to
+ if (!newPos.equals(oldPos)) {
+ oldPos.setOffset(newPos.offset);
+ oldPos.setLength(newPos.length);
+ modifications.add(foldingAnnotation);
+ }
+ } else {
+ deletions.add(foldingAnnotation);
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Searches the given {@link DirtyRegion} for annotations that now have a length
+ * of 0. This is caused when something that was being folded has been deleted.
+ * These {@link FoldingAnnotation}s are then added to the {@link List} of
+ * {@link FoldingAnnotation}s to be deleted
+ * </p>
+ *
+ * @param dirtyRegion find the now invalid {@link FoldingAnnotation}s in this
+ * {@link DirtyRegion}
+ * @param deletions the current list of {@link FoldingAnnotation}s marked for
+ * deletion that the newly found invalid
+ * {@link FoldingAnnotation}s will be added to
+ */
+ protected void markInvalidAnnotationsForDeletion(DirtyRegion dirtyRegion, List<FoldingAnnotation> deletions,
+ List<FoldingAnnotation> existing) {
+ Iterator<Annotation> iter = getAnnotationIterator(dirtyRegion);
+ if (iter != null) {
+ while (iter.hasNext()) {
+ Annotation anno = iter.next();
+ if (anno instanceof FoldingAnnotation) {
+ FoldingAnnotation folding = (FoldingAnnotation) anno;
+ Position pos = projectionAnnotationModel.getPosition(anno);
+ if (pos.length == 0) {
+ deletions.add(folding);
+ } else {
+ existing.add(folding);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void reconcile(IRegion partition) {
+ // not used, we use:
+ // reconcile(DirtyRegion dirtyRegion, IRegion subRegion)
+ }
+
+ @Override
+ public void setProgressMonitor(IProgressMonitor monitor) {
+ // Do nothing
+ }
+
+ @Override
+ public void initialReconcile() {
+ reconcile(new DirtyRegion(0, document.getLength(), DirtyRegion.INSERT, document.get()), null);
+ }
+}
\ No newline at end of file |