aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorangelozerr2018-08-01 11:35:42 -0400
committerMickael Istria2018-08-03 03:15:45 -0400
commitfe1f173989deb9f16974d0c78d55f68c04a07462 (patch)
treed75e3894334ac161536142c44bf560273a6d37e5
parent839c84221733f9fc9c6adc7a0a23d4132d79b92a (diff)
downloadeclipse.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>
-rw-r--r--org.eclipse.ui.genericeditor.examples/plugin.xml9
-rw-r--r--org.eclipse.ui.genericeditor.tests/plugin.xml17
-rw-r--r--org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/FoldingTest.java118
-rw-r--r--org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java1
-rw-r--r--org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingReconciler.java33
-rw-r--r--org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/FoldingStrategy.java157
-rw-r--r--org.eclipse.ui.genericeditor/plugin.properties1
-rw-r--r--org.eclipse.ui.genericeditor/plugin.xml1
-rw-r--r--org.eclipse.ui.genericeditor/schema/foldingReconcilers.exsd155
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java69
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java108
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/DefaultFoldingReconciler.java68
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java471
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 48f28ce0b..28f8ba11b 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 1d81989bb..a65580a09 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 000000000..d8d66bf8e
--- /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 ee4c19407..6ab16ce9d 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 000000000..7a13de4b7
--- /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 000000000..aeae31763
--- /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 725ae4c04..492dde2b6 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 78ddce654..3840a7bd3 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 000000000..f23cc301e
--- /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 &lt;code&gt;org.eclipse.jface.text.reconciler.IReconciler&lt;/code&gt;
+ 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:
+
+ * &lt;with variable=&quot;viewer&quot;/&gt; : use it if your expression requires the viewer.
+ * &lt;with variable=&quot;editor&quot;/&gt; : use it if your expression requires the editor.
+ * &lt;with variable=&quot;editorInput&quot;/&gt; : 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:
+&lt;pre&gt;
+&lt;extension point=&quot;org.eclipse.ui.genericeditor.foldingReconcilers&quot;&gt;
+ &lt;foldingReconciler
+ class=&quot;org.eclipse.ui.genericeditor.examples.TargetDefinitionFoldingReconciler&quot;
+ contentType=&quot;org.eclipse.pde.targetFile&quot;&gt;
+ &lt;enabledWhen&gt;
+ &lt;with variable=&quot;editor&quot;&gt;
+ &lt;test property=&quot;org.eclipse.ui.genericeditor.examples.TargetDefinitionPropertyTester&quot;&gt;
+ &lt;/test&gt;
+ &lt;/with&gt;
+ &lt;/enabledWhen&gt;
+ &lt;/foldingReconciler&gt;
+&lt;/extension&gt;
+&lt;/pre&gt;
+ </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 &lt;a href=&quot;http://www.eclipse.org/legal/epl-v10.html&quot;&gt;http://www.eclipse.org/legal/epl-v10.html&lt;/a&gt;
+ </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 1d149b3b3..44f283265 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 74e8183d7..148ef13c7 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 000000000..01ab3fb73
--- /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 000000000..1fa316121
--- /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