diff --git a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTest.java b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTest.java index cd5f544ab48..47469488f72 100644 --- a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTest.java +++ b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTest.java @@ -24,14 +24,19 @@ import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.preference.IPreferenceStore; + import org.eclipse.jface.text.IRegion; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jdt.ui.tests.core.rules.ProjectTestSetup; +import org.eclipse.jdt.internal.ui.JavaPlugin; + public class FoldingTest { @Rule public ProjectTestSetup projectSetup= new ProjectTestSetup(); @@ -50,7 +55,9 @@ public void setUp() throws CoreException { sourceFolder= JavaProjectHelper.addSourceContainer(jProject, "src"); } packageFragment= sourceFolder.createPackageFragment("org.example.test", false, null); - } + IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore(); + store.setValue(PreferenceConstants.EDITOR_NEW_FOLDING_ENABLED, true); + } @After public void tearDown() throws CoreException { @@ -117,7 +124,7 @@ public void foo() { //here should be an annotation List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // Javadoc - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 7); // foo Methode + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 6); // foo Methode } @Test @@ -136,8 +143,8 @@ public void bar() { //here should be an annotation FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // foo Methode - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 7); // bar Methode + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 3); // foo Methode + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 6); // bar Methode } @Test @@ -155,8 +162,8 @@ void bar() { //here should be an annotation FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 6); // InnerClass - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 5); // bar Methode + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 5); // InnerClass + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 4); // bar Methode } @Test @@ -181,9 +188,9 @@ void bar() { //here should be an annotation List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // OuterWithDocs Javadoc - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 12); // InnerWithDocs Klasse + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 11); // InnerWithDocs Klasse FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 6, 8); // InnerWithDocs Javadoc - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 9, 11); // bar Methode + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 9, 10); // bar Methode } @Test @@ -228,21 +235,22 @@ class h { * */ void b() { //here should be an annotation - /* //here should NOT be an annotation + /* //here should be an annotation * */ int a; } } """; - FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 5); + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 6); List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 1, 3); // 1. Javadoc FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 4, 6); // 2. Javadoc FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 7, 9); // 3. Javadoc FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 12, 14); // 4. Javadoc - FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 15, 20); // Methode b() + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 15, 19); // Methode b() + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 16, 18); // 5. Javadoc } @Test @@ -257,4 +265,237 @@ class SomeClass {} """; FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 1); } + + @Test + public void testMethodDeclarationFoldingWithSameLineStart() throws Exception { + String str= """ + package org.example.test; + public class Q { + void a() { + int i = 0; + }void b() { + + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 3); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 4, 5); // 2. Method + } + + @Test + public void testIfStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class D { + void x() { //here should be an annotation + if (true) { //here should be an annotation + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 3); // if + } + + @Test + public void testTryStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class E { + void x() { //here should be an annotation + try { //here should be an annotation + + } catch (Exception e) { //here should be an annotation + + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 3); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 7); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 4); // try + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 6); // catch + } + + @Test + public void testWhileStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class F { + void x() { //here should be an annotation + while (true) { //here should be an annotation + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 3); // while + } + + @Test + public void testForStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class G { + void x() { //here should be an annotation + for(int i=0;i<1;i++){ //here should be an annotation + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 3); // for + } + + @Test + public void testEnhancedForStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class H { + void x() { //here should be an annotation + for(String s: new String[0]){ //here should be an annotation + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 3); // for + } + + @Test + public void testDoStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class I { + void x() { //here should be an annotation + do { //here should be an annotation + + } while(false); + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 5); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 4); // do + } + + @Test + public void testSynchronizedStatementFolding() throws Exception { + String str= """ + package org.example.test; + public class K { + void x() { //here should be an annotation + synchronized(this) { //here should be an annotation + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 4); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 3); // synchronized + } + + @Test + public void testLambdaExpressionFolding() throws Exception { + String str= """ + package org.example.test; + import java.util.function.Supplier; + public class L { + void x() { //here should be an annotation + Supplier s = () -> { //here should be an annotation + return ""; + }; + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 6); // 1. Method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 4, 5); // Supplier + } + + @Test + public void testAnonymousClassDeclarationFolding() throws Exception { + String str= """ + package org.example.test; + public class M { + Object o = new Object(){ //here should be an annotation + void y() { //here should be an annotation + + } + }; + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 2); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 5); // Object + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 4); // Method + } + + @Test + public void testEnumDeclarationFolding() throws Exception { + String str= """ + package org.example.test; + public enum N { //here should be an annotation + A, + B + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 1); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 1, 3); // enum + } + + @Test + public void testInitializerFolding() throws Exception { + String str= """ + package org.example.test; + public class O { + static { //here should be an annotation + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 1); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 2); // static + } + + @Test + public void testNestedFolding() throws Exception { + String str= """ + package org.example.test; + public class P { + void x() { //here should be an annotation + if (true) { //here should be an annotation + for(int i=0;i<1;i++){ //here should be an annotation + while(true) { //here should be an annotation + do { //here should be an annotation + } while(false); + } + } + } + } + } + """; + FoldingTestUtils.assertCodeHasRegions(packageFragment, "TestFolding.java", str, 5); + List regions= FoldingTestUtils.getProjectionRangesOfFile(packageFragment, "TestFolding.java", str); + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 2, 10); // method + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 3, 9); // if + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 4, 8); // for + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 5, 7); // while + FoldingTestUtils.assertContainsRegionUsingStartAndEndLine(regions, str, 6, 6); // do + } } diff --git a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTestUtils.java b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTestUtils.java index 31bd0d0f6b8..8e5f5f33697 100644 --- a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTestUtils.java +++ b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/folding/FoldingTestUtils.java @@ -79,12 +79,14 @@ public static void assertContainsRegionUsingStartAndEndLine(List projec ", actual regions: " + projectionRanges ); } + private static int getLengthIfNotFound(String input, int startLineEnd) { if (startLineEnd == -1) { startLineEnd= input.length(); } return startLineEnd; } + private static int findLineStartIndex(String input, int lineNumber) { int currentInputIndex= 0; for (int i= 0; i < lineNumber; i++) { @@ -95,6 +97,7 @@ private static int findLineStartIndex(String input, int lineNumber) { } return currentInputIndex; } + private static int findNextLineStart(String input, int currentInputIndex) { return input.indexOf('\n', currentInputIndex + 1); } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/DefaultJavaFoldingPreferenceBlock.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/DefaultJavaFoldingPreferenceBlock.java index b8f64cb15ab..43510a919e0 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/DefaultJavaFoldingPreferenceBlock.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/DefaultJavaFoldingPreferenceBlock.java @@ -26,6 +26,7 @@ import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.jface.preference.IPreferenceStore; @@ -75,7 +76,7 @@ private OverlayKey[] createKeys() { overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_FOLDING_METHODS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_FOLDING_IMPORTS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_FOLDING_HEADERS)); - + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_NEW_FOLDING_ENABLED)); return overlayKeys.toArray(new OverlayKey[overlayKeys.size()]); } @@ -87,21 +88,44 @@ public Control createControl(Composite composite) { fOverlayStore.load(); fOverlayStore.start(); + composite.setLayout(new GridLayout(1, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite inner= new Composite(composite, SWT.NONE); - GridLayout layout= new GridLayout(1, true); - layout.verticalSpacing= 3; + GridLayout layout= new GridLayout(1, false); + layout.verticalSpacing= 10; layout.marginWidth= 0; + layout.marginHeight= 0; inner.setLayout(layout); - - Label label= new Label(inner, SWT.LEFT); - label.setText(FoldingMessages.DefaultJavaFoldingPreferenceBlock_title); - - addCheckBox(inner, FoldingMessages.DefaultJavaFoldingPreferenceBlock_comments, PreferenceConstants.EDITOR_FOLDING_JAVADOC, 0); - addCheckBox(inner, FoldingMessages.DefaultJavaFoldingPreferenceBlock_headers, PreferenceConstants.EDITOR_FOLDING_HEADERS, 0); - addCheckBox(inner, FoldingMessages.DefaultJavaFoldingPreferenceBlock_innerTypes, PreferenceConstants.EDITOR_FOLDING_INNERTYPES, 0); - addCheckBox(inner, FoldingMessages.DefaultJavaFoldingPreferenceBlock_methods, PreferenceConstants.EDITOR_FOLDING_METHODS, 0); - addCheckBox(inner, FoldingMessages.DefaultJavaFoldingPreferenceBlock_imports, PreferenceConstants.EDITOR_FOLDING_IMPORTS, 0); - + inner.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Group initialFoldGroup = new Group(inner, SWT.NONE); + GridLayout initialFoldLayout = new GridLayout(1, false); + initialFoldLayout.marginWidth = 10; + initialFoldLayout.marginHeight = 10; + initialFoldGroup.setLayout(initialFoldLayout); + initialFoldGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + initialFoldGroup.setText(FoldingMessages.DefaultJavaFoldingPreferenceBlock_title); + + addCheckBox(initialFoldGroup, FoldingMessages.DefaultJavaFoldingPreferenceBlock_comments, PreferenceConstants.EDITOR_FOLDING_JAVADOC, 0); + addCheckBox(initialFoldGroup, FoldingMessages.DefaultJavaFoldingPreferenceBlock_headers, PreferenceConstants.EDITOR_FOLDING_HEADERS, 0); + addCheckBox(initialFoldGroup, FoldingMessages.DefaultJavaFoldingPreferenceBlock_innerTypes, PreferenceConstants.EDITOR_FOLDING_INNERTYPES, 0); + addCheckBox(initialFoldGroup, FoldingMessages.DefaultJavaFoldingPreferenceBlock_methods, PreferenceConstants.EDITOR_FOLDING_METHODS, 0); + addCheckBox(initialFoldGroup, FoldingMessages.DefaultJavaFoldingPreferenceBlock_imports, PreferenceConstants.EDITOR_FOLDING_IMPORTS, 0); + + Group extendedFoldingGroup= new Group(inner, SWT.NONE); + GridLayout extendedFoldingLayout= new GridLayout(1, false); + extendedFoldingLayout.marginWidth= 10; + extendedFoldingLayout.marginHeight= 10; + extendedFoldingGroup.setLayout(extendedFoldingLayout); + extendedFoldingGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + extendedFoldingGroup.setText(FoldingMessages.DefaultJavaFoldingPreferenceBlock_New_Setting_Title); + Label label= new Label(extendedFoldingGroup, SWT.WRAP); + GridData gd = new GridData(SWT.FILL, SWT.TOP, true, false); + gd.widthHint = 300; + label.setLayoutData(gd); + label.setText(FoldingMessages.DefaultJavaFoldingPreferenceBlock_Warning_New_Feature); + addCheckBox(extendedFoldingGroup, FoldingMessages.DefaultJavaFoldingPreferenceBlock_New, PreferenceConstants.EDITOR_NEW_FOLDING_ENABLED, 0); return inner; } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.java index 92b4ea1637c..12483f4b345 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.java @@ -31,10 +31,12 @@ private FoldingMessages() { public static String DefaultJavaFoldingPreferenceBlock_innerTypes; public static String DefaultJavaFoldingPreferenceBlock_methods; public static String DefaultJavaFoldingPreferenceBlock_imports; + public static String DefaultJavaFoldingPreferenceBlock_New; public static String DefaultJavaFoldingPreferenceBlock_headers; public static String EmptyJavaFoldingPreferenceBlock_emptyCaption; public static String JavaFoldingStructureProviderRegistry_warning_providerNotFound_resetToDefault; - + public static String DefaultJavaFoldingPreferenceBlock_New_Setting_Title; + public static String DefaultJavaFoldingPreferenceBlock_Warning_New_Feature; static { NLS.initializeMessages(BUNDLE_NAME, FoldingMessages.class); } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.properties index 3a91f5e6f6f..f12af7f490e 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.properties +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/folding/FoldingMessages.properties @@ -13,13 +13,15 @@ ############################################################################### -DefaultJavaFoldingPreferenceBlock_title= Initially fold these elements: +DefaultJavaFoldingPreferenceBlock_title= Initially fold these elements DefaultJavaFoldingPreferenceBlock_comments= &Comments DefaultJavaFoldingPreferenceBlock_innerTypes= Inner &types DefaultJavaFoldingPreferenceBlock_methods= &Members DefaultJavaFoldingPreferenceBlock_imports= &Imports DefaultJavaFoldingPreferenceBlock_headers= &Header Comments - +DefaultJavaFoldingPreferenceBlock_New = &Activate feature +DefaultJavaFoldingPreferenceBlock_New_Setting_Title = &Extended folding (Experimental) JavaFoldingStructureProviderRegistry_warning_providerNotFound_resetToDefault= The ''{0}'' folding provider could not be found. Resetting to the default folding provider. +DefaultJavaFoldingPreferenceBlock_Warning_New_Feature= &Enable folding for other code blocks. Activating this feature may cause a significant performance degradation. EmptyJavaFoldingPreferenceBlock_emptyCaption= diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java index 0cc78f9cd48..51760a8ffc6 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java @@ -3412,6 +3412,16 @@ private PreferenceConstants() { */ public static final String EDITOR_FOLDING_ENABLED= "editor_folding_enabled"; //$NON-NLS-1$ + /** + * A named preference that controls whether the new or the old folding is used. + *

+ * Value is of type Boolean. + *

+ * + * @since 3.34 + */ + public static final String EDITOR_NEW_FOLDING_ENABLED= "editor_new_folding_enabled"; //$NON-NLS-1$ + /** * A named preference that stores the configured folding provider. *

@@ -4388,6 +4398,8 @@ public static void initializeDefaultValues(IPreferenceStore store) { store.setDefault(EDITOR_JAVA_CODEMINING_DEFAULT_FILTER_FOR_PARAMETER_NAMES, true); store.setDefault(EDITOR_JAVA_CODEMINING_SHOW_PARAMETER_NAME_SINGLE_ARG, true); + store.setDefault(EDITOR_NEW_FOLDING_ENABLED, false); + // Javadoc hover & view JavaElementLinks.initDefaultPreferences(store); } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/folding/DefaultJavaFoldingStructureProvider.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/folding/DefaultJavaFoldingStructureProvider.java index e212f37c39a..380a005f898 100755 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/folding/DefaultJavaFoldingStructureProvider.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/folding/DefaultJavaFoldingStructureProvider.java @@ -29,6 +29,8 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; @@ -68,7 +70,28 @@ import org.eclipse.jdt.core.compiler.IScanner; import org.eclipse.jdt.core.compiler.ITerminalSymbols; import org.eclipse.jdt.core.compiler.InvalidInputException; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.EnhancedForStatement; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.ForStatement; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Initializer; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.SynchronizedStatement; +import org.eclipse.jdt.core.dom.TryStatement; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.WhileStatement; import org.eclipse.jdt.ui.PreferenceConstants; @@ -298,7 +321,6 @@ public String toString() { } } - private static final class Tuple { JavaProjectionAnnotation annotation; Position position; @@ -308,6 +330,283 @@ private static final class Tuple { } } + private class FoldingVisitor extends ASTVisitor { + + private FoldingStructureComputationContext ctx; + + public FoldingVisitor(FoldingStructureComputationContext ctx) { + this.ctx= ctx; + } + + @Override + public boolean visit(CompilationUnit node) { + List imports= node.imports(); + if (imports.size() > 1) { + int start= imports.get(0).getStartPosition(); + ImportDeclaration lastImport= imports.get(imports.size() - 1); + int end= lastImport.getStartPosition() + lastImport.getLength(); + includelastLine = true; + createFoldingRegion(start, end - start, ctx.collapseMembers()); + } + return super.visit(node); + } + + @Override + public boolean visit(TypeDeclaration node) { + boolean isInnerClass= node.isMemberTypeDeclaration(); + if (isInnerClass) { + int start= node.getName().getStartPosition(); + int end= node.getStartPosition() + node.getLength(); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + } + return true; + } + + @Override + public boolean visit(MethodDeclaration node) { + int start= node.getName().getStartPosition(); + int end= node.getStartPosition() + node.getLength(); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + return true; + } + + @Override + public boolean visit(IfStatement node) { + int start= node.getStartPosition(); + int end= getEndPosition(node.getThenStatement()); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + node.getThenStatement().accept(this); + if (node.getElseStatement() != null) { + if (node.getElseStatement() instanceof IfStatement) { + Statement elseIfStatement= node.getElseStatement(); + start= findElseKeywordStart(elseIfStatement); + end= getEndPosition(((IfStatement) elseIfStatement).getThenStatement()); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + node.getElseStatement().accept(this); + } else { + start= findElseKeywordStart(node.getElseStatement()); + end= getEndPosition(node.getElseStatement()); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + node.getElseStatement().accept(this); + } + } + return false; + } + + @Override + public boolean visit(TryStatement node) { + createFoldingRegionForTryBlock(node); + node.getBody().accept(this); + for (Object obj : node.catchClauses()) { + CatchClause catchClause= (CatchClause) obj; + createFoldingRegionForCatchClause(catchClause); + catchClause.accept(this); + } + if (node.getFinally() != null) { + createFoldingRegionForFinallyBlock(node); + node.getFinally().accept(this); + } + return false; + } + + @Override + public boolean visit(WhileStatement node) { + createFoldingRegionForStatement(node); + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(ForStatement node) { + createFoldingRegionForStatement(node); + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(EnhancedForStatement node) { + createFoldingRegionForStatement(node); + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(DoStatement node) { + createFoldingRegionForStatement(node); + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(SynchronizedStatement node) { + createFoldingRegion(node, ctx.collapseMembers()); + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(LambdaExpression node) { + if (node.getBody() instanceof Block) { + createFoldingRegion(node.getBody(), ctx.collapseMembers()); + node.getBody().accept(this); + } + return false; + } + + @Override + public boolean visit(AnonymousClassDeclaration node) { + createFoldingRegion(node, ctx.collapseMembers()); + return true; + } + + @Override + public boolean visit(EnumDeclaration node) { + createFoldingRegion(node, ctx.collapseMembers()); + return true; + } + + @Override + public boolean visit(Initializer node) { + createFoldingRegion(node, ctx.collapseMembers()); + return true; + } + + private void createFoldingRegion(ASTNode node, boolean collapse) { + createFoldingRegion(node.getStartPosition(), node.getLength(), collapse); + } + + private void createFoldingRegion(int start, int length, boolean collapse) { + if (length > 0) { + IRegion region= new Region(start, length); + IRegion aligned= alignRegion(region, ctx); + + if (aligned != null && isMultiline(aligned)) { + Position position= new Position(aligned.getOffset(), aligned.getLength()); + JavaProjectionAnnotation annotation= new JavaProjectionAnnotation(collapse, null, false); + ctx.addProjectionRange(annotation, position); + } + } + } + + private boolean isMultiline(IRegion region) { + try { + IDocument document= ctx.getDocument(); + int startLine= document.getLineOfOffset(region.getOffset()); + int endLine= document.getLineOfOffset(region.getOffset() + region.getLength()); + return endLine > startLine; + } catch (BadLocationException e) { + return false; + } + } + + private int findElseKeywordStart(ASTNode node) { + try { + IDocument document= ctx.getDocument(); + int startSearch= node.getParent().getStartPosition(); + int endSearch= node.getStartPosition(); + + String text= document.get(startSearch, endSearch - startSearch); + int index= text.lastIndexOf("else"); //$NON-NLS-1$ + if (index >= 0) { + return startSearch + index; + } + } catch (BadLocationException e) { + } + return node.getStartPosition(); + } + + private int getEndPosition(Statement statement) { + if (statement instanceof Block) { + return statement.getStartPosition() + statement.getLength(); + } else { + try { + IDocument document= ctx.getDocument(); + int start= statement.getStartPosition(); + int line= document.getLineOfOffset(start); + return document.getLineOffset(line + 1); + } catch (BadLocationException e) { + return statement.getStartPosition() + statement.getLength(); + } + } + } + + private void createFoldingRegionForStatement(ASTNode node) { + int start= node.getStartPosition(); + int length= node.getLength(); + if (!(node instanceof Block)) { + try { + IDocument document= ctx.getDocument(); + int endLine= document.getLineOfOffset(start + length - 1); + if (endLine + 1 < document.getNumberOfLines()) { + String currentIndent= getIndentOfLine(document, endLine); + String nextIndent= getIndentOfLine(document, endLine + 1); + if (nextIndent.length() > currentIndent.length()) { + int nextLineEndOffset= document.getLineOffset(endLine + 2) - 1; + length= nextLineEndOffset - start; + } else { + length= document.getLineOffset(endLine + 1) - start; + } + } else { + length= document.getLength() - start; + } + } catch (BadLocationException e) { + } + } + createFoldingRegion(start, length, ctx.collapseMembers()); + } + + private String getIndentOfLine(IDocument document, int line) throws BadLocationException { + IRegion region= document.getLineInformation(line); + int lineStart= region.getOffset(); + int lineLength= region.getLength(); + + int whiteSpaceEnd= lineStart; + while (whiteSpaceEnd < lineStart + lineLength) { + char c= document.getChar(whiteSpaceEnd); + if (!Character.isWhitespace(c)) { + break; + } + whiteSpaceEnd++; + } + return document.get(lineStart, whiteSpaceEnd - lineStart); + } + + private void createFoldingRegionForTryBlock(TryStatement node) { + int start= node.getStartPosition(); + int end= getEndPosition(node.getBody()); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + } + + private void createFoldingRegionForCatchClause(CatchClause catchClause) { + int start= catchClause.getStartPosition(); + int end= getEndPosition(catchClause.getBody()); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + } + + private void createFoldingRegionForFinallyBlock(TryStatement node) { + Block finallyBlock= node.getFinally(); + int start= findFinallyKeywordStart(node); + int end= getEndPosition(finallyBlock); + createFoldingRegion(start, end - start, ctx.collapseMembers()); + } + + private int findFinallyKeywordStart(TryStatement node) { + try { + IDocument document= ctx.getDocument(); + int startSearch= node.getStartPosition(); + int endSearch= node.getFinally().getStartPosition(); + String text= document.get(startSearch, endSearch - startSearch); + int index= text.lastIndexOf("finally"); //$NON-NLS-1$ + + if (index >= 0) { + return startSearch + index; + } + } catch (BadLocationException e) { + } + return node.getFinally().getStartPosition(); + } + } + /** * Filter for annotations. */ @@ -422,7 +721,6 @@ private boolean shouldIgnoreDelta(CompilationUnit ast, IJavaElementDelta delta) IJavaElement elem= SelectionConverter.getElementAtOffset(ast.getTypeRoot(), new TextSelection(editor.getCachedSelectedRange().x, editor.getCachedSelectedRange().y)); if (!(elem instanceof IImportDeclaration)) return false; - } } catch (JavaModelException e) { return false; // can't compute @@ -469,10 +767,11 @@ private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta del } } + /** - * Projection position that will return two foldable regions: one folding away - * the region from after the '/**' to the beginning of the content, the other - * from after the first content line until after the comment. + * Projection position that will return two foldable regions: one folding away the region from + * after the '/**' to the beginning of the content, the other from after the first content line + * until after the comment. */ private static final class CommentPosition extends Position implements IProjectionPosition { CommentPosition(int offset, int length) { @@ -497,7 +796,6 @@ public IRegion[] computeProjectionRegions(IDocument document) throws BadLocation IRegion preRegion; if (firstLine < captionLine) { -// preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd); int preOffset= document.getLineOffset(firstLine); IRegion preEndLineInfo= document.getLineInformation(captionLine); int preEnd= preEndLineInfo.getOffset(); @@ -524,13 +822,12 @@ public IRegion[] computeProjectionRegions(IDocument document) throws BadLocation } /** - * Finds the offset of the first identifier part within content. - * Returns 0 if none is found. + * Finds the offset of the first identifier part within content. Returns 0 if + * none is found. * * @param content the content to search * @param prefixEnd the end of the prefix - * @return the first index of a unicode identifier part, or zero if none can - * be found + * @return the first index of a unicode identifier part, or zero if none can be found */ private int findFirstContent(final CharSequence content, int prefixEnd) { int lenght= content.length(); @@ -541,33 +838,6 @@ private int findFirstContent(final CharSequence content, int prefixEnd) { return 0; } -// /** -// * Finds the offset of the first identifier part within content. -// * Returns 0 if none is found. -// * -// * @param content the content to search -// * @return the first index of a unicode identifier part, or zero if none can -// * be found -// */ -// private int findPrefixEnd(final CharSequence content) { -// // return the index after the leading '/*' or '/**' -// int len= content.length(); -// int i= 0; -// while (i < len && isWhiteSpace(content.charAt(i))) -// i++; -// if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*') -// if (len >= i + 3 && content.charAt(i + 2) == '*') -// return i + 3; -// else -// return i + 2; -// else -// return i; -// } -// -// private boolean isWhiteSpace(char c) { -// return c == ' ' || c == '\t'; -// } - /* * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument) */ @@ -650,10 +920,8 @@ public IRegion[] computeProjectionRegions(IDocument document) throws BadLocation return new IRegion[] { preRegion, postRegion }; } } - if (preRegion != null) return new IRegion[] { preRegion }; - return null; } @@ -671,7 +939,6 @@ public int computeCaptionOffset(IDocument document) throws BadLocationException } catch (JavaModelException e) { // ignore and use default } - return nameStart - offset; } @@ -721,11 +988,22 @@ public void projectionDisabled() { } } + boolean includelastLine = false; + /* context and listeners */ private JavaEditor fEditor; private ProjectionListener fProjectionListener; private IJavaElement fInput; private IElementChangedListener fElementListener; + private IPropertyChangeListener fPropertyChangeListener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if (PreferenceConstants.EDITOR_NEW_FOLDING_ENABLED.equals(event.getProperty())) { + fNewFolding = event.getNewValue().equals(Boolean.TRUE); + } + initialize(); + } + }; /* preferences */ private boolean fCollapseJavadoc= false; @@ -733,6 +1011,7 @@ public void projectionDisabled() { private boolean fCollapseInnerTypes= true; private boolean fCollapseMembers= false; private boolean fCollapseHeaderComments= true; + private boolean fNewFolding; /* filters */ /** Member filter, matches nested members (but not top-level types). */ @@ -778,6 +1057,8 @@ public void install(ITextEditor editor, ProjectionViewer viewer) { if (editor instanceof JavaEditor) { fProjectionListener= new ProjectionListener(viewer); fEditor= (JavaEditor)editor; + IPreferenceStore store = JavaPlugin.getDefault().getPreferenceStore(); + store.addPropertyChangeListener(fPropertyChangeListener); } } @@ -801,6 +1082,10 @@ private void internalUninstall() { fProjectionListener.dispose(); fProjectionListener= null; fEditor= null; + IPreferenceStore store = JavaPlugin.getDefault().getPreferenceStore(); + if (store != null && fPropertyChangeListener != null) { + store.removePropertyChangeListener(fPropertyChangeListener); + } } } @@ -872,7 +1157,6 @@ private FoldingStructureComputationContext createInitialContext() { fInput= getInputElement(); if (fInput == null) return null; - return createContext(true); } @@ -889,7 +1173,6 @@ private FoldingStructureComputationContext createContext(boolean allowCollapse) IScanner scanner= null; if (fUpdatingCount == 1) scanner= fSharedScanner; // reuse scanner - return new FoldingStructureComputationContext(doc, model, allowCollapse, scanner); } @@ -900,12 +1183,13 @@ private IJavaElement getInputElement() { } private void initializePreferences() { - IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore(); - fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES); - fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS); - fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC); - fCollapseMembers= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS); - fCollapseHeaderComments= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS); + IPreferenceStore store = JavaPlugin.getDefault().getPreferenceStore(); + fCollapseInnerTypes = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES); + fCollapseImportContainer = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS); + fCollapseJavadoc = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC); + fCollapseMembers = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS); + fCollapseHeaderComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS); + fNewFolding = store.getBoolean(PreferenceConstants.EDITOR_NEW_FOLDING_ENABLED); } private void update(FoldingStructureComputationContext ctx) { @@ -935,7 +1219,7 @@ private void update(FoldingStructureComputationContext ctx) { * position update and keep the old range, in order to keep the folding structure * stable. */ - boolean isMalformedAnonymousType= newPosition.getOffset() == 0 && element.getElementType() == IJavaElement.TYPE && isInnerType((IType) element); + boolean isMalformedAnonymousType= newPosition.getOffset() == 0 && element != null && element.getElementType() == IJavaElement.TYPE && isInnerType((IType) element); List annotations= oldStructure.get(element); if (annotations == null) { if (!isMalformedAnonymousType) @@ -990,6 +1274,103 @@ private void update(FoldingStructureComputationContext ctx) { } private void computeFoldingStructure(FoldingStructureComputationContext ctx) { + if (fNewFolding && fInput instanceof ICompilationUnit) { + processCompilationUnit((ICompilationUnit) fInput, ctx); + processComments(ctx); + } else { + processSourceReference(ctx); + } + } + + private void processCompilationUnit(ICompilationUnit unit, FoldingStructureComputationContext ctx) { + try { + String source = unit.getSource(); + if (source == null) return; + + ctx.getScanner().setSource(source.toCharArray()); + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setBindingsRecovery(true); + parser.setStatementsRecovery(true); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + parser.setResolveBindings(true); + parser.setUnitName(unit.getElementName()); + parser.setProject(unit.getJavaProject()); + parser.setSource(unit); + Map options = unit.getJavaProject().getOptions(true); + options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_23); + options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_23); + options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_23); + options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); + parser.setCompilerOptions(options); + + CompilationUnit ast = (CompilationUnit) parser.createAST(null); + ast.accept(new FoldingVisitor(ctx)); + } catch (JavaModelException | IllegalStateException e) { + } + } + + private void processComments(FoldingStructureComputationContext ctx) { + try { + IDocument document = ctx.getDocument(); + String source = document.get(); + IScanner scanner = ctx.getScanner(); + scanner.setSource(source.toCharArray()); + scanner.resetTo(0, source.length() - 1); + + int token; + while ((token = scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) { + if (token == ITerminalSymbols.TokenNameCOMMENT_BLOCK || token == ITerminalSymbols.TokenNameCOMMENT_JAVADOC) { + int start = scanner.getCurrentTokenStartPosition(); + int end = scanner.getCurrentTokenEndPosition() + 1; + try { + int endLine = document.getLineOfOffset(end); + int lineOffset = document.getLineOffset(endLine); + int lineLength = document.getLineLength(endLine); + String lineText = document.get(lineOffset, lineLength); + int commentEndInLine = end - lineOffset; + String afterComment = lineText.substring(commentEndInLine); + + if (afterComment.trim().length() > 0) { + end = lineOffset; + } else { + if (endLine + 1 < document.getNumberOfLines()) { + end = document.getLineOffset(endLine + 1); + } else { + end = document.getLength(); + } + } + } catch (BadLocationException e) { + } + + IRegion region = new Region(start, end - start); + includelastLine = true; + IRegion aligned = alignRegion(region, ctx); + + if (aligned != null && isMultiline(aligned, ctx)) { + Position position = createCommentPosition(aligned); + JavaProjectionAnnotation annotation = new JavaProjectionAnnotation(ctx.collapseJavadoc(), null, true); + ctx.addProjectionRange(annotation, position); + } + } + } + } catch (InvalidInputException e) { + } + } + + + private boolean isMultiline(IRegion region, FoldingStructureComputationContext ctx) { + try { + IDocument document = ctx.getDocument(); + int startLine = document.getLineOfOffset(region.getOffset()); + int endLine = document.getLineOfOffset(region.getOffset() + region.getLength() - 1); + return endLine > startLine; + } catch (BadLocationException e) { + return false; + } + } + + + private void processSourceReference(FoldingStructureComputationContext ctx) { IParent parent= (IParent) fInput; try { if (!(fInput instanceof ISourceReference)) @@ -1004,6 +1385,58 @@ private void computeFoldingStructure(FoldingStructureComputationContext ctx) { } } + /** + * Aligns region to start and end at a line offset. The region's start is + * decreased to the next line offset, and the end offset increased to the next line start or the + * end of the document. null is returned if region is + * null itself or does not comprise at least one line delimiter, as a single line + * cannot be folded. + * + * @param region the region to align, may be null + * @param ctx the folding context + * @return a region equal or greater than region that is aligned with line + * offsets, null if the region is too small to be foldable (e.g. covers + * only one line) + */ + protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) { + if (region == null) + return null; + + IDocument document= ctx.getDocument(); + + try { + int start= document.getLineOfOffset(region.getOffset()); + int end= document.getLineOfOffset(region.getOffset() - 1 + region.getLength()); + if (start >= end) + return null; + int offset= document.getLineOffset(start); + int endOffset; + if (includelastLine) { + endOffset= document.getLineOffset(end + 1); + includelastLine = false; + } + else { + endOffset= document.getLineOffset(end); + } + + return new Region(offset, endOffset - offset); + } catch (BadLocationException x) { + return null; + } + } + + /** + * Creates a comment folding position from an + * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned} + * region. + * + * @param aligned an aligned region + * @return a folding position corresponding to aligned + */ + protected Position createCommentPosition(IRegion aligned) { + return new CommentPosition(aligned.getOffset(), aligned.getLength()); + } + private void computeFoldingStructure(IJavaElement[] elements, FoldingStructureComputationContext ctx) throws JavaModelException { for (IJavaElement element : elements) { computeFoldingStructure(element, ctx); @@ -1018,8 +1451,8 @@ private void computeFoldingStructure(IJavaElement[] elements, FoldingStructureCo /** * Computes the folding structure for a given {@link IJavaElement java element}. Computed * projection annotations are - * {@link DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) added} - * to the computation context. + * {@link DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext#addProjectionRange(DefaultJavaFoldingStructureProvider.JavaProjectionAnnotation, Position) + * added} to the computation context. *

* Subclasses may extend or replace. The default implementation creates projection annotations * for the following elements: @@ -1027,21 +1460,21 @@ private void computeFoldingStructure(IJavaElement[] elements, FoldingStructureCo *

    *
  • true members (not for top-level types)
  • *
  • the javadoc comments of any member
  • - *
  • header comments (javadoc or multi-line comments appearing before the first type's - * javadoc or before the package or import declarations).
  • + *
  • header comments (javadoc or multi-line comments appearing before the first type's javadoc + * or before the package or import declarations).
  • *
* * @param element the java element to compute the folding structure for * @param ctx the computation context */ protected void computeFoldingStructure(IJavaElement element, FoldingStructureComputationContext ctx) { - boolean collapse= false; boolean collapseCode= true; switch (element.getElementType()) { case IJavaElement.IMPORT_CONTAINER: collapse= ctx.collapseImportContainer(); + includelastLine= true; break; case IJavaElement.TYPE: collapseCode= isInnerType((IType) element) && !isAnonymousEnum((IType) element); @@ -1060,8 +1493,10 @@ protected void computeFoldingStructure(IJavaElement element, FoldingStructureCom if (regions.length > 0) { // comments for (int i= 0; i < regions.length - 1; i++) { + includelastLine= true; IRegion normalized= alignRegion(regions[i], ctx); if (normalized != null) { + includelastLine= true; Position position= createCommentPosition(normalized); if (position != null) { boolean commentCollapse; @@ -1078,7 +1513,14 @@ protected void computeFoldingStructure(IJavaElement element, FoldingStructureCom if (collapseCode) { IRegion normalized= alignRegion(regions[regions.length - 1], ctx); if (normalized != null) { - Position position= element instanceof IMember ? createMemberPosition(normalized, (IMember) element) : createCommentPosition(normalized); + Position position; + if (element instanceof IMember) { + position= createMemberPosition(normalized, (IMember) element); + } else { + includelastLine= true; + position= createCommentPosition(normalized); + } + if (position != null) ctx.addProjectionRange(new JavaProjectionAnnotation(collapse, element, false), position); } @@ -1178,7 +1620,6 @@ protected final IRegion[] computeProjectionRanges(ISourceReference reference, Fo return result; } catch (JavaModelException | InvalidInputException e) { } - return new IRegion[0]; } @@ -1234,18 +1675,6 @@ private IRegion computeHeaderComment(FoldingStructureComputationContext ctx) thr return null; } - /** - * Creates a comment folding position from an - * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned} - * region. - * - * @param aligned an aligned region - * @return a folding position corresponding to aligned - */ - protected final Position createCommentPosition(IRegion aligned) { - return new CommentPosition(aligned.getOffset(), aligned.getLength()); - } - /** * Creates a folding position that remembers its member from an * {@link #alignRegion(IRegion, DefaultJavaFoldingStructureProvider.FoldingStructureComputationContext) aligned} @@ -1259,47 +1688,6 @@ protected final Position createMemberPosition(IRegion aligned, IMember member) { return new JavaElementPosition(aligned.getOffset(), aligned.getLength(), member); } - /** - * Aligns region to start and end at a line offset. The region's start is - * decreased to the next line offset, and the end offset increased to the next line start or the - * end of the document. null is returned if region is - * null itself or does not comprise at least one line delimiter, as a single line - * cannot be folded. - * - * @param region the region to align, may be null - * @param ctx the folding context - * @return a region equal or greater than region that is aligned with line - * offsets, null if the region is too small to be foldable (e.g. covers - * only one line) - */ - protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) { - if (region == null) - return null; - - IDocument document= ctx.getDocument(); - - try { - - int start= document.getLineOfOffset(region.getOffset()); - int end= document.getLineOfOffset(region.getOffset() + region.getLength()); - if (start >= end) - return null; - - int offset= document.getLineOffset(start); - int endOffset; - if (document.getNumberOfLines() > end + 1) - endOffset= document.getLineOffset(end + 1); - else - endOffset= document.getLineOffset(end) + document.getLineLength(end); - - return new Region(offset, endOffset - offset); - - } catch (BadLocationException x) { - // concurrent modification - return null; - } - } - private ProjectionAnnotationModel getModel() { return fEditor.getAdapter(ProjectionAnnotationModel.class); } @@ -1513,4 +1901,4 @@ private void modifyFiltered(Filter filter, boolean expand) { model.modifyAnnotations(null, null, modified.toArray(new Annotation[modified.size()])); } -} +} \ No newline at end of file