diff --git a/org.eclipse.jdt.ls.core/plugin.xml b/org.eclipse.jdt.ls.core/plugin.xml
index ac9cac2097..6493455232 100644
--- a/org.eclipse.jdt.ls.core/plugin.xml
+++ b/org.eclipse.jdt.ls.core/plugin.xml
@@ -5,6 +5,7 @@
+
@@ -225,7 +226,6 @@
id="org.eclipse.jdt.core.javanature">
-
+
+
+
+
+
+
+
+ This extension point represents different kinds of code completion handlers for the JDT LS.
+Each extension must implement <code>org.eclipse.jdt.ls.core.internal.handlers.ICompletionHandler</code>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ a fully qualified identifier of the target extension point
+
+
+
+
+
+
+ an optional identifier of the extension instance
+
+
+
+
+
+
+ an optional name of the extension instance
+
+
+
+
+
+
+
+
+
+
+
+ A unique identifier that can be used to reference this ICompletionHandler.
+
+
+
+
+
+
+ The class that implements this completion handler. The class must implement ICompletionHandler.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following is an example of a language server content provider extension:
+
+<pre>
+ <extension
+ id="completionHandler"
+ point="org.eclipse.jdt.ls.core.completionHandler">
+ <completionHandler
+ id="someCompletionHandler"
+ class="com.example.SomeCompletionHandler" />
+ </extension>
+</pre>
+
+
+
+
+
+
+
diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java
index 60fc5f01f4..6ea508a7f8 100644
--- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java
+++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java
@@ -35,7 +35,7 @@
import org.eclipse.jdt.ls.core.internal.commands.VmCommand;
import org.eclipse.jdt.ls.core.internal.framework.protobuf.ProtobufSupport;
import org.eclipse.jdt.ls.core.internal.handlers.BundleUtils;
-import org.eclipse.jdt.ls.core.internal.handlers.CompletionHandler;
+import org.eclipse.jdt.ls.core.internal.handlers.CompletionHandlers;
import org.eclipse.jdt.ls.core.internal.handlers.CreateModuleInfoHandler;
import org.eclipse.jdt.ls.core.internal.handlers.FormatterHandler;
import org.eclipse.jdt.ls.core.internal.handlers.PasteEventHandler;
@@ -191,10 +191,10 @@ public Object executeCommand(String commandId, List
+
+
+
diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java
index 665f498a99..4d3d40f997 100644
--- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java
+++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java
@@ -106,7 +106,7 @@ public class CompletionHandlerTest extends AbstractCompilationUnitBasedTest {
private DocumentLifeCycleHandler lifeCycleHandler;
private JavaClientConnection javaClient;
- private static String COMPLETION_TEMPLATE =
+ static String COMPLETION_TEMPLATE =
"{\n" +
" \"id\": \"1\",\n" +
" \"method\": \"textDocument/completion\",\n" +
@@ -4150,8 +4150,12 @@ private CompletionList requestCompletions(ICompilationUnit unit, String complete
return server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight();
}
- private String createCompletionRequest(ICompilationUnit unit, int line, int kar) {
- return COMPLETION_TEMPLATE.replace("${file}", JDTUtils.toURI(unit))
+ static String createCompletionRequest(ICompilationUnit unit, int line, int kar) {
+ return createCompletionRequest(JDTUtils.toURI(unit), line, kar);
+ }
+
+ static String createCompletionRequest(String uri, int line, int kar) {
+ return COMPLETION_TEMPLATE.replace("${file}", uri)
.replace("${line}", String.valueOf(line))
.replace("${char}", String.valueOf(kar));
}
@@ -4165,6 +4169,10 @@ private void mockLSP2Client() {
}
private void mockLSPClient(boolean isSnippetSupported, boolean isSignatureHelpSupported) {
+ mockLSPClient(preferenceManager, isSnippetSupported, isSignatureHelpSupported);
+ }
+
+ static void mockLSPClient(PreferenceManager preferenceManager, boolean isSnippetSupported, boolean isSignatureHelpSupported) {
// Mock the preference manager to use LSP v3 support.
when(preferenceManager.getClientPreferences().isCompletionSnippetsSupported()).thenReturn(isSnippetSupported);
when(preferenceManager.getClientPreferences().isSignatureHelpSupported()).thenReturn(isSignatureHelpSupported);
diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionRankingProviderTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionRankingProviderTest.java
index 7b793317d2..c8098674b0 100644
--- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionRankingProviderTest.java
+++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionRankingProviderTest.java
@@ -110,7 +110,7 @@ public void testOnDidCompletionItemSelect() throws Exception {ICompilationUnit u
"}\n");
requestCompletions(unit, "Integer.");
- CompletionHandler handler = new CompletionHandler(JavaLanguageServerPlugin.getPreferencesManager());
+ CompletionHandlers handler = new CompletionHandlers(JavaLanguageServerPlugin.getPreferencesManager());
ArgumentCaptor argument = ArgumentCaptor.forClass(CompletionItem.class);
handler.onDidCompletionItemSelect(String.valueOf((new CompletionResponse()).getId() - 1), "0");
diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ContributedCompletionHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ContributedCompletionHandlerTest.java
new file mode 100644
index 0000000000..9aeeb487c8
--- /dev/null
+++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ContributedCompletionHandlerTest.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) Simeon Andreev 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
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.ls.core.internal.handlers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.manipulation.CoreASTProvider;
+import org.eclipse.jdt.ls.core.internal.JsonMessageHelper;
+import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionList;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+/**
+ * @author Simeon Andreev
+ *
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class ContributedCompletionHandlerTest extends AbstractCompilationUnitBasedTest {
+
+ private IFile file;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ ContributedTestCompletionHandler.item = ContributedTestCompletionHandler.testItem();
+ mockLSP3Client();
+ CoreASTProvider sharedASTProvider = CoreASTProvider.getInstance();
+ sharedASTProvider.disposeAST();
+ file = project.getFile("test.txt");
+ file.create(new String("test").getBytes(), IResource.FORCE, monitor);
+ }
+
+ @AfterEach
+ public void removeExtension() throws CoreException {
+ ContributedTestCompletionHandler.item = null;
+ file.delete(true, monitor);
+ }
+
+ @Test
+ public void testCompletion_contributedHandler() throws Exception {
+ when(preferenceManager.getClientPreferences().isCompletionItemLabelDetailsSupport()).thenReturn(true);
+ CompletionList list = requestCompletions(file.getLocationURI().toString(), 0, 0);
+ assertNotNull(list);
+ List filtered = list.getItems().stream().filter((item) -> {
+ return item.getDetail() != null && item.getDetail().startsWith(ContributedTestCompletionHandler.TEST_DETAIL);
+ }).collect(Collectors.toList());
+ assertEquals(1, filtered.size(), "No test proposals");
+ CompletionItem oride = filtered.get(0);
+ assertEquals(ContributedTestCompletionHandler.TEST_CONTENT, oride.getInsertText());
+ }
+
+ private CompletionList requestCompletions(String uri, int line, int offset) throws JavaModelException {
+ return server.completion(JsonMessageHelper.getParams(CompletionHandlerTest.createCompletionRequest(uri, line, offset))).join().getRight();
+ }
+
+ private void mockLSP3Client() {
+ CompletionHandlerTest.mockLSPClient(preferenceManager, true, true);
+ }
+}
diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ContributedTestCompletionHandler.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ContributedTestCompletionHandler.java
new file mode 100644
index 0000000000..6650900638
--- /dev/null
+++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ContributedTestCompletionHandler.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Simeon Andreev 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:
+ * Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.ls.core.internal.handlers;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionList;
+import org.eclipse.lsp4j.CompletionParams;
+
+/**
+ * @author Simeon Andreev
+ *
+ */
+public class ContributedTestCompletionHandler implements ICompletionHandler {
+
+ public static final String TEST_DETAIL = "test completion item";
+ public static final String TEST_CONTENT = "test string";
+
+ public static CompletionItem item = null;
+
+ @Override
+ public CompletionList completion(CompletionParams params, IProgressMonitor monitor) {
+ List items = Collections.emptyList();
+ if (item != null) {
+ items = Arrays.asList(item);
+ }
+ return new CompletionList(items);
+ }
+
+ public static CompletionItem testItem() {
+ CompletionItem item = new CompletionItem();
+ item.setInsertText(TEST_CONTENT);
+ item.setDetail(TEST_DETAIL);
+ return item;
+ }
+}
diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SignatureHelpHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SignatureHelpHandlerTest.java
index 07867c8eb8..0a5c83a649 100644
--- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SignatureHelpHandlerTest.java
+++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SignatureHelpHandlerTest.java
@@ -1223,8 +1223,8 @@ public foo() {
CompletionProposalRequestor collector = new CompletionProposalRequestor(cu, offset, preferenceManager);
cu.codeComplete(offset, collector, monitor);
- CompletionHandler.selectedProposal = collector.getProposals().get(0);
- StringBuilder description = CompletionProposalDescriptionProvider.createMethodProposalDescription(CompletionHandler.selectedProposal);
+ CompletionHandlers.selectedProposal = collector.getProposals().get(0);
+ StringBuilder description = CompletionProposalDescriptionProvider.createMethodProposalDescription(CompletionHandlers.selectedProposal);
String fromProposal = description.toString();
String unnamedResult = "String(byte[] arg0, int arg1, int arg2, Charset arg3)";
String namedResult = "String(byte[] bytes, int offset, int length, Charset charset)";