diff --git a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
index 7ac73965a..dacd4fd52 100644
--- a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tests for language server bundle (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e.test;singleton:=true
-Bundle-Version: 0.15.29.qualifier
+Bundle-Version: 0.15.30.qualifier
Fragment-Host: org.eclipse.lsp4e
Bundle-Vendor: Eclipse LSP4E
Bundle-RequiredExecutionEnvironment: JavaSE-17
diff --git a/org.eclipse.lsp4e.test/pom.xml b/org.eclipse.lsp4e.test/pom.xml
index 4f1415c95..5e4998f17 100644
--- a/org.eclipse.lsp4e.test/pom.xml
+++ b/org.eclipse.lsp4e.test/pom.xml
@@ -8,7 +8,7 @@
org.eclipse.lsp4e.test
eclipse-test-plugin
- 0.15.29-SNAPSHOT
+ 0.15.30-SNAPSHOT
diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/folding/FoldingCommandsTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/folding/FoldingCommandsTest.java
new file mode 100644
index 000000000..42003420b
--- /dev/null
+++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/folding/FoldingCommandsTest.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vegard IT GmbH and others.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Sebastian Thomschke (Vegard IT GmbH) - initial implementation
+ *******************************************************************************/
+package org.eclipse.lsp4e.test.folding;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.source.Annotation;
+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.lsp4e.LSPEclipseUtils;
+import org.eclipse.lsp4e.LanguageServerPlugin;
+import org.eclipse.lsp4e.test.utils.AbstractTest;
+import org.eclipse.lsp4e.test.utils.TestUtils;
+import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
+import org.eclipse.lsp4e.ui.FoldingPreferencePage;
+import org.eclipse.lsp4j.FoldingRange;
+import org.eclipse.lsp4j.FoldingRangeKind;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.IHandlerService;
+import org.junit.Test;
+
+public class FoldingCommandsTest extends AbstractTest {
+
+ private static final int MAX_WAIT_MS = 5_000;
+
+ private static final String CONTENT = """
+ /**
+ * SPDX-License-Identifier: EPL-2.0
+ */
+ import
+ import
+ import
+ /**
+ * Some comment
+ */
+ visible
+ """;
+
+ @Test
+ public void foldAndUnfoldAllCommands() throws Exception {
+ // Ensure no auto-folding interferes with the command behavior
+ configureAutoFolding(false);
+
+ // Provide folding ranges from the Mock LS: license header and imports
+ final var foldingRangeLicense = new FoldingRange(0, 2);
+ foldingRangeLicense.setKind(FoldingRangeKind.Comment);
+ final var foldingRangeImport = new FoldingRange(3, 5);
+ foldingRangeImport.setKind(FoldingRangeKind.Imports);
+ MockLanguageServer.INSTANCE.setFoldingRanges(List.of(foldingRangeLicense, foldingRangeImport));
+
+ // Open editor and wait until folding annotations are present
+ final var editor = TestUtils.openEditor(TestUtils.createUniqueTestFile(null, CONTENT));
+ final ITextViewer viewer = LSPEclipseUtils.getTextViewer(editor);
+ assertTrue(viewer instanceof ProjectionViewer);
+
+ final var pViewer = (ProjectionViewer) viewer;
+ TestUtils.waitForAndAssertCondition(MAX_WAIT_MS, () -> getAnnotationModel(pViewer) != null);
+ final ProjectionAnnotationModel model = getAnnotationModel(pViewer);
+
+ // Ensure folding annotations are populated after projection model exists
+ // by toggling the folding-enabled preference to trigger a reconcile.
+ IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore();
+ store.setValue(FoldingPreferencePage.PREF_FOLDING_ENABLED, false);
+ store.setValue(FoldingPreferencePage.PREF_FOLDING_ENABLED, true);
+
+ TestUtils.waitForAndAssertCondition(MAX_WAIT_MS, () -> countAnnotations(model) == 2);
+ assertEquals(2, countAnnotations(model));
+
+ // Execute "Fold All"
+ IHandlerService handlerService = PlatformUI.getWorkbench().getService(IHandlerService.class);
+ handlerService.executeCommand("org.eclipse.lsp4e.folding.collapseAll", null);
+
+ TestUtils.waitForAndAssertCondition(MAX_WAIT_MS, () -> countCollapsed(model) == 2);
+ assertEquals(2, countCollapsed(model));
+
+ // Execute "Unfold All"
+ handlerService.executeCommand("org.eclipse.lsp4e.folding.expandAll", null);
+ TestUtils.waitForAndAssertCondition(MAX_WAIT_MS, () -> countCollapsed(model) == 0);
+ assertEquals(0, countCollapsed(model));
+ }
+
+ private static ProjectionAnnotationModel getAnnotationModel(ProjectionViewer viewer) {
+ return viewer.getProjectionAnnotationModel();
+ }
+
+ private static int countAnnotations(ProjectionAnnotationModel model) {
+ int count = 0;
+ for (var it = model.getAnnotationIterator(); it != null && it.hasNext();) {
+ if (it.next() instanceof ProjectionAnnotation) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private static int countCollapsed(ProjectionAnnotationModel model) {
+ int count = 0;
+ for (var it = model.getAnnotationIterator(); it != null && it.hasNext();) {
+ Annotation a = it.next();
+ if (a instanceof ProjectionAnnotation pa && pa.isCollapsed()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private static void configureAutoFolding(boolean enabled) {
+ IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore();
+ store.setValue(FoldingPreferencePage.PREF_FOLDING_ENABLED, true);
+ store.setValue(FoldingPreferencePage.PREF_AUTOFOLD_COMMENTS, enabled);
+ store.setValue(FoldingPreferencePage.PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS, enabled);
+ store.setValue(FoldingPreferencePage.PREF_AUTOFOLD_REGIONS, enabled);
+ store.setValue(FoldingPreferencePage.PREF_AUTOFOLD_IMPORT_STATEMENTS, enabled);
+ }
+}
diff --git a/org.eclipse.lsp4e/META-INF/MANIFEST.MF b/org.eclipse.lsp4e/META-INF/MANIFEST.MF
index 6ad4bc5b5..2487e8291 100644
--- a/org.eclipse.lsp4e/META-INF/MANIFEST.MF
+++ b/org.eclipse.lsp4e/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Language Server Protocol client for Eclipse IDE (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e;singleton:=true
-Bundle-Version: 0.18.30.qualifier
+Bundle-Version: 0.18.31.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0",
org.eclipse.equinox.common;bundle-version="3.8.0",
diff --git a/org.eclipse.lsp4e/plugin.properties b/org.eclipse.lsp4e/plugin.properties
index 7046593a7..114aac434 100644
--- a/org.eclipse.lsp4e/plugin.properties
+++ b/org.eclipse.lsp4e/plugin.properties
@@ -47,4 +47,9 @@ command.open.quick.type.hierarchy.description = Open Quick Call Hierarchy for th
view.callHierarchy.name = Call Hierarchy
view.typeHierarchy.name = Type Hierarchy
view.languageServers.name = Language Servers
-viewsCategory.name = Language Servers
\ No newline at end of file
+viewsCategory.name = Language Servers
+
+# Folding commands
+command.folding.foldAll.name = Fold All
+command.folding.unfoldAll.name = Unfold All
+menu.folding.name = Folding
diff --git a/org.eclipse.lsp4e/plugin.xml b/org.eclipse.lsp4e/plugin.xml
index e4506139c..9e1f2dbf6 100644
--- a/org.eclipse.lsp4e/plugin.xml
+++ b/org.eclipse.lsp4e/plugin.xml
@@ -154,6 +154,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -464,7 +564,7 @@
contentType="org.eclipse.core.runtime.text">
-
+
diff --git a/org.eclipse.lsp4e/pom.xml b/org.eclipse.lsp4e/pom.xml
index f8a9f9958..d2a46aab4 100644
--- a/org.eclipse.lsp4e/pom.xml
+++ b/org.eclipse.lsp4e/pom.xml
@@ -10,7 +10,7 @@
org.eclipse.lsp4e
eclipse-plugin
- 0.18.30-SNAPSHOT
+ 0.18.31-SNAPSHOT
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/folding/FoldAllHandler.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/folding/FoldAllHandler.java
new file mode 100644
index 000000000..b45cbcde9
--- /dev/null
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/folding/FoldAllHandler.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vegard IT GmbH and others.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Sebastian Thomschke (Vegard IT GmbH) - initial implementation.
+ *******************************************************************************/
+package org.eclipse.lsp4e.operations.folding;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.lsp4e.ui.UI;
+
+public class FoldAllHandler extends AbstractHandler {
+
+ @Override
+ public @Nullable Object execute(final ExecutionEvent event) {
+ if (UI.getActiveTextViewer() instanceof final ProjectionViewer viewer && viewer.isProjectionMode()) {
+ UI.runOnUIThread(() -> viewer.doOperation(ProjectionViewer.COLLAPSE_ALL));
+ }
+ return null;
+ }
+}
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/folding/UnfoldAllHandler.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/folding/UnfoldAllHandler.java
new file mode 100644
index 000000000..906f9862a
--- /dev/null
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/folding/UnfoldAllHandler.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vegard IT GmbH and others.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Sebastian Thomschke (Vegard IT GmbH) - initial implementation.
+ *******************************************************************************/
+package org.eclipse.lsp4e.operations.folding;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.lsp4e.ui.UI;
+
+public class UnfoldAllHandler extends AbstractHandler {
+
+ @Override
+ public @Nullable Object execute(final ExecutionEvent event) {
+ if (UI.getActiveTextViewer() instanceof final ProjectionViewer viewer && viewer.isProjectionMode()) {
+ UI.runOnUIThread(() -> viewer.doOperation(ProjectionViewer.EXPAND_ALL));
+ }
+ return null;
+ }
+}