Bug 520659 - [generic editor] Default Code folding for generic editor
should use IndentFoldingStrategy

Change-Id: If023933536584f07960fcb6b99c6dedcf6f6be1c
Signed-off-by: angelozerr <angelo.zerr@gmail.com>
diff --git a/org.eclipse.ui.genericeditor.examples/plugin.xml b/org.eclipse.ui.genericeditor.examples/plugin.xml
index 48f28ce..28f8ba1 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 1d81989..a65580a 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 0000000..d8d66bf
--- /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 ee4c194..6ab16ce 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 @@
 		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 0000000..7a13de4
--- /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 0000000..aeae317
--- /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 725ae4c..492dde2 100644
--- a/org.eclipse.ui.genericeditor/plugin.properties
+++ b/org.eclipse.ui.genericeditor/plugin.properties
@@ -18,6 +18,7 @@
 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 78ddce6..3840a7b 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 0000000..f23cc30
--- /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 1d149b3..44f2832 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.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 @@
 
 	/**
 	 *
-	 * @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 @@
 	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 @@
 
 	@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 @@
 
 	@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 @@
 	@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 @@
 	@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 @@
 	@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 @@
 
 	@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 74e8183..148ef13 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.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 @@
 
 	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 @@
 		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 @@
 				.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 @@
 				.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 @@
 
 	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 @@
 		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 0000000..01ab3fb
--- /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 0000000..1fa3161
--- /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