diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java index 91569da93c..94040a9b8f 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -59,6 +60,7 @@ import org.eclipse.lsp4j.SemanticTokensServerFull; import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.TextDocumentContentRegistrationOptions; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.eclipse.lsp4j.TextDocumentSyncOptions; import org.eclipse.lsp4j.WorkspaceFoldersOptions; @@ -201,6 +203,11 @@ public void registerCapabilities(InitializeResult initializeResult) { capabilities.setTextDocumentSync(textDocumentSyncOptions); WorkspaceServerCapabilities wsCapabilities = new WorkspaceServerCapabilities(); + if (!preferenceManager.getClientPreferences().isTextDocumentContentDynamicRegistrationSupported()) { + TextDocumentContentRegistrationOptions textDocumentContentOptions = new TextDocumentContentRegistrationOptions(); + textDocumentContentOptions.setSchemes(Collections.singletonList("jdt")); + wsCapabilities.setTextDocumentContent(textDocumentContentOptions); + } WorkspaceFoldersOptions wsFoldersOptions = new WorkspaceFoldersOptions(); wsFoldersOptions.setSupported(Boolean.TRUE); wsFoldersOptions.setChangeNotifications(Boolean.TRUE); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java index 35c0638768..690ebfd43a 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java @@ -153,6 +153,9 @@ import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SignatureHelpParams; import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.TextDocumentContentParams; +import org.eclipse.lsp4j.TextDocumentContentRegistrationOptions; +import org.eclipse.lsp4j.TextDocumentContentResult; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.TypeDefinitionParams; @@ -373,6 +376,11 @@ private void registerCapabilities() { if (preferenceManager.getClientPreferences().isWorkspaceSymbolDynamicRegistered()) { registerCapability(Preferences.WORKSPACE_SYMBOL_ID, Preferences.WORKSPACE_SYMBOL); } + if (preferenceManager.getClientPreferences().isTextDocumentContentDynamicRegistrationSupported()) { + TextDocumentContentRegistrationOptions options = new TextDocumentContentRegistrationOptions(); + options.setSchemes(Collections.singletonList("jdt")); + registerCapability(Preferences.TEXT_DOCUMENT_CONTENT_ID, Preferences.TEXT_DOCUMENT_CONTENT, options); + } if (!preferenceManager.getClientPreferences().isClientDocumentSymbolProviderRegistered() && preferenceManager.getClientPreferences().isDocumentSymbolDynamicRegistered()) { registerCapability(Preferences.DOCUMENT_SYMBOL_ID, Preferences.DOCUMENT_SYMBOL); } @@ -1000,6 +1008,14 @@ public CompletableFuture willRenameFiles(RenameFilesParams params }); } + @Override + public CompletableFuture textDocumentContent(TextDocumentContentParams params) { + debugTrace(">> workspace/textDocumentContent"); + ContentProviderManager handler = JavaLanguageServerPlugin.getContentProviderManager(); + URI uri = JDTUtils.toURI(params.getUri()); + return computeAsync((monitor) -> new TextDocumentContentResult(handler.getContent(uri, monitor))); + } + /* (non-Javadoc) * @see org.eclipse.jdt.ls.core.internal.JavaProtocolExtensions#ClassFileContents(org.eclipse.lsp4j.TextDocumentIdentifier) */ diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/ClientPreferences.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/ClientPreferences.java index f6a07dd6ad..4a436097cd 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/ClientPreferences.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/ClientPreferences.java @@ -151,6 +151,10 @@ public boolean isWorkspaceSymbolDynamicRegistered() { return v3supported && capabilities.getWorkspace() != null && isDynamicRegistrationSupported(capabilities.getWorkspace().getSymbol()); } + public boolean isTextDocumentContentDynamicRegistrationSupported() { + return v3supported && capabilities.getWorkspace() != null && isDynamicRegistrationSupported(capabilities.getWorkspace().getTextDocumentContent()); + } + public boolean isWorkspaceChangeWatchedFilesDynamicRegistered() { return v3supported && capabilities.getWorkspace() != null && isDynamicRegistrationSupported(capabilities.getWorkspace().getDidChangeWatchedFiles()); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java index c00a288d75..4de05678b3 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java @@ -594,6 +594,7 @@ public class Preferences { public static final String TEXT_DOCUMENT_RENAME = "textDocument/rename"; public static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand"; public static final String WORKSPACE_SYMBOL = "workspace/symbol"; + public static final String TEXT_DOCUMENT_CONTENT = "workspace/textDocumentContent"; public static final String WORKSPACE_WATCHED_FILES = "workspace/didChangeWatchedFiles"; public static final String DOCUMENT_SYMBOL = "textDocument/documentSymbol"; public static final String COMPLETION = "textDocument/completion"; @@ -617,6 +618,7 @@ public class Preferences { public static final String RENAME_ID = UUID.randomUUID().toString(); public static final String EXECUTE_COMMAND_ID = UUID.randomUUID().toString(); public static final String WORKSPACE_SYMBOL_ID = UUID.randomUUID().toString(); + public static final String TEXT_DOCUMENT_CONTENT_ID = UUID.randomUUID().toString(); public static final String DOCUMENT_SYMBOL_ID = UUID.randomUUID().toString(); public static final String COMPLETION_ID = UUID.randomUUID().toString(); public static final String CODE_ACTION_ID = UUID.randomUUID().toString(); diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceTextDocumentContentTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceTextDocumentContentTest.java new file mode 100644 index 0000000000..2886870460 --- /dev/null +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceTextDocumentContentTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.ls.core.internal.handlers; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URI; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.ls.core.internal.ClassFileUtil; +import org.eclipse.jdt.ls.core.internal.JDTUtils; +import org.eclipse.jdt.ls.core.internal.WorkspaceHelper; +import org.eclipse.jdt.ls.core.internal.decompiler.FernFlowerDecompiler; +import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest; +import org.eclipse.lsp4j.TextDocumentContentParams; +import org.eclipse.lsp4j.TextDocumentContentResult; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class WorkspaceTextDocumentContentTest extends AbstractProjectsManagerBasedTest { + + private JDTLanguageServer server; + private IProject project; + + @BeforeEach + public void setup() throws Exception { + importProjects("maven/salut"); + project = WorkspaceHelper.getProject("salut"); + server = new JDTLanguageServer(projectsManager, preferenceManager); + } + + @AfterEach + public void teardown() throws Exception { + if (server != null) { + server.shutdown(); + } + } + + @Test + public void testTextDocumentContent() throws Exception { + URI uri = JDTUtils.toURI(ClassFileUtil.getURI(project, "org.apache.commons.lang3.text.WordUtils")); + + TextDocumentContentParams params = new TextDocumentContentParams(); + params.setUri(uri.toString()); + + TextDocumentContentResult result = server.textDocumentContent(params).get(); + + assertNotNull(result); + assertNotNull(result.getText()); + assertTrue(result.getText().contains("Operations on Strings that contain words."), "unexpected body content: " + result.getText()); + } + + @Test + public void testTextDocumentContentDecompile() throws Exception { + URI uri = JDTUtils.toURI(ClassFileUtil.getURI(project, "java.math.BigDecimal")); + + TextDocumentContentParams params = new TextDocumentContentParams(); + params.setUri(uri.toString()); + + TextDocumentContentResult result = server.textDocumentContent(params).get(); + + assertNotNull(result); + assertNotNull(result.getText()); + assertTrue(result.getText().startsWith(FernFlowerDecompiler.DECOMPILER_HEADER), "disassembler header missing from " + result.getText()); + assertTrue(result.getText().contains("class BigDecimal"), "unexpected body content: " + result.getText()); + } + + @Test + public void testTextDocumentContentMissingFile() throws Exception { + URI uri = JDTUtils.toURI("file:///this/is/Missing.class"); + + TextDocumentContentParams params = new TextDocumentContentParams(); + params.setUri(uri.toString()); + + TextDocumentContentResult result = server.textDocumentContent(params).get(); + + assertNotNull(result); + assertNotNull(result.getText()); + assertTrue(result.getText().isEmpty(), "expected empty content, got: " + result.getText()); + } +}