Skip to content

Add completion handler extension point#3738

Open
trancexpress wants to merge 1 commit into
eclipse-jdtls:mainfrom
trancexpress:completion-handler-extension
Open

Add completion handler extension point#3738
trancexpress wants to merge 1 commit into
eclipse-jdtls:mainfrom
trancexpress:completion-handler-extension

Conversation

@trancexpress

@trancexpress trancexpress commented Mar 12, 2026

Copy link
Copy Markdown

This change adds an extension point which can be used to contribute a completion handler.

A completion handler can be contributed as follows:

<plugin>
   <extension
      id="contributedCompletionHandler"
      point="org.eclipse.jdt.ls.core.completionHandler">
      <completionHandler
         id="testHandler"
        class="my.test.ContributedCompletionHandler" />
   </extension>
</plugin>

The handler must implement the interface:

org.eclipse.jdt.ls.core.internal.handlers.ICompletionHandler

Offered code completions are the aggregated completions of the JDT LS CompletionHandler and the contributed completion handlers.

The handler which generated a completion is also the handler which is asked to complete it, if the completion is selected.

@eclipse-ls-bot

Copy link
Copy Markdown
Contributor

Can one of the admins verify this patch?

@trancexpress

Copy link
Copy Markdown
Author

@fbricon could you take a quick look here? Is this change going in the right direction, or you had something else in mind? #3719 (comment)

I've started with the most simple part of what we want to change, the code completions.

If this is the right direction, I have some questions as well:

  1. What do you expect for the aggregation of CompletionItemDefaults?

  2. I'm also not sure what to do with this case of CompletionHandler usage:

org.eclipse.jdt.ls.core.internal.JDTDelegateCommandHandler.executeCommand(String, List<Object>, IProgressMonitor)

If plug-ins are contributing completion handlers, is it expected that this usage is also covered?

@trancexpress trancexpress force-pushed the completion-handler-extension branch 3 times, most recently from f992c28 to 4ef603e Compare March 13, 2026 08:41
@fbricon

fbricon commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

@trancexpress from a cursory look, I think you're on the right track. Indeed we need to figure out how to handle default items, and how to also connect completion resolution, I'll try to give it more time later today

@fbricon

fbricon commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

add to whitelist

@trancexpress

trancexpress commented Mar 13, 2026

Copy link
Copy Markdown
Author

Thank you @fbricon ! There is no rush here, it will take some time before I can try the changes out.

Though I think I've gotten the PR to a state where there is not much room for error when hooking our code...

@trancexpress trancexpress force-pushed the completion-handler-extension branch 3 times, most recently from fd200fa to bc644c0 Compare March 16, 2026 11:50
* @author Simeon Andreev
*
*/
public class ExtensionRepository<T> implements IRegistryEventListener {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will be useful for other extension points.

@trancexpress trancexpress force-pushed the completion-handler-extension branch 3 times, most recently from 8956ce9 to 184727b Compare March 16, 2026 15:04
@trancexpress trancexpress marked this pull request as ready for review March 16, 2026 15:05
@trancexpress

Copy link
Copy Markdown
Author

I'll check if it makes sense to add more tests, otherwise I think this PR is ready for a review.

@trancexpress

trancexpress commented Mar 16, 2026

Copy link
Copy Markdown
Author

The test job failed with:

	Tests (11 failures / +6)

    org.eclipse.jdt.ls.core.internal.handlers.DiagnosticHandlerTest.testNotUsed
    org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandlerTest.testRemoveDeadCodeAfterIf
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testBuildHelperSupport
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testUpdate
    org.eclipse.jdt.ls.core.internal.preferences.NullAnalysisTest.testNullAnalysisDisabled
    org.eclipse.jdt.ls.core.internal.preferences.NullAnalysisTest.testMissingNonNull
    org.eclipse.jdt.ls.core.internal.managers.MavenProjectImporterTest.testImportSimpleJavaProject
    org.eclipse.jdt.ls.core.internal.correction.ConvertVarQuickFixTest.testConvertVarTypeToResolvedType
    org.eclipse.jdt.ls.core.internal.correction.ConvertVarQuickFixTest.testConvertResolvedTypeToVar
    org.eclipse.jdt.ls.core.internal.handlers.WorkspaceDiagnosticsHandlerTest.testProjectConfigurationIsNotUpToDate
    org.eclipse.jdt.ls.core.internal.handlers.WorkspaceDiagnosticsHandlerTest.testTaskMarkers

I see no failures when I run those test cases locally.

When I run all tests locally (select org.eclipse.jdt.ls.tests -> Run as JUnit Plug-in Test), on main branch and my branch (completion-handler-extension), I see 7 skipped tests, 3 errors and 10 failures (same errors/fails on both branches):

image

I ran tests also on command line: JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./mvnw clean verify -U

main:


Failures: 
  ProtobufSupportTest.testGenerateProtobufSources:64 expected: <true> but was: <false>
  DiagnosticHandlerTest.testNotUsed:185 expected: <0> but was: <1>
  JavaFXTest.testJavaFX:74->AbstractProjectsManagerBasedTest.assertNoErrors:401 java8fx has errors: 
Type=org.eclipse.jdt.core.problem:Message=The import javafx cannot be resolved:LineNumber=3, Type=org.eclipse.jdt.core.problem:Message=The import javafx cannot be resolved:LineNumber=4, Type=org.eclipse.jdt.core.problem:Message=Application cannot be resolved to a type:LineNumber=6, Type=org.eclipse.jdt.core.problem:Message=The method launch(String[]) is undefined for the type Foo:LineNumber=9, Type=org.eclipse.jdt.core.problem:Message=Stage cannot be resolved to a type:LineNumber=13 ==> expected: <0> but was: <5>
  GradleProjectImporterTest.testNameConflictProject2:647->AbstractGradleBasedTest.assertIsGradleProject:45 expected: not <null>
Errors: 
  ProtobufSupportTest.testAfterImportsForProtobuf:53 NullPointer Cannot invoke "java.util.List.size()" because "notifications" is null
  GradleProjectImporterTest.testProtoBufSupport:593 » JavaModel protobuf does not exist
  GradleProjectImporterTest.testProtoBufSupportChanged:611 » JavaModel protobuf does not exist

Tests run: 2076, Failures: 4, Errors: 3, Skipped: 7

completion-handler-extension:

Failures: 
  ProtobufSupportTest.testGenerateProtobufSources:64 expected: <true> but was: <false>
  DiagnosticHandlerTest.testDeprecated:210 expected: <1> but was: <2>
  DiagnosticHandlerTest.testNotUsed:185 expected: <0> but was: <1>
  JavaFXTest.testJavaFX:74->AbstractProjectsManagerBasedTest.assertNoErrors:401 java8fx has errors: 
Type=org.eclipse.jdt.core.problem:Message=The import javafx cannot be resolved:LineNumber=3, Type=org.eclipse.jdt.core.problem:Message=The import javafx cannot be resolved:LineNumber=4, Type=org.eclipse.jdt.core.problem:Message=Application cannot be resolved to a type:LineNumber=6, Type=org.eclipse.jdt.core.problem:Message=The method launch(String[]) is undefined for the type Foo:LineNumber=9, Type=org.eclipse.jdt.core.problem:Message=Stage cannot be resolved to a type:LineNumber=13 ==> expected: <0> but was: <5>
  GradleProjectImporterTest.testNameConflictProject2:647->AbstractGradleBasedTest.assertIsGradleProject:45 expected: not <null>
Errors: 
  ProtobufSupportTest.testAfterImportsForProtobuf:53 NullPointer Cannot invoke "java.util.List.size()" because "notifications" is null
  GradleProjectImporterTest.testProtoBufSupport:593 » JavaModel protobuf does not exist
  GradleProjectImporterTest.testProtoBufSupportChanged:611 » JavaModel protobuf does not exist

Tests run: 2077, Failures: 5, Errors: 3, Skipped: 7

DiagnosticHandlerTest.testDeprecated seems to be the only difference.

org.opentest4j.AssertionFailedError: expected: <0> but was: <1>
        at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
        at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145)
        at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:531)
        at org.eclipse.jdt.ls.core.internal.handlers.DiagnosticHandlerTest.testNotUsed(DiagnosticHandlerTest.java:185)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

@fbricon

fbricon commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

I know some errors fail locally because they require running Eclipse with lombok. Errors on the main branch might be caused by some recent updates to the Eclipse 4.40 bits. You can try to reload your target platform to check.

@trancexpress

Copy link
Copy Markdown
Author

Errors on the main branch might be caused by some recent updates to the Eclipse 4.40 bits. You can try to reload your target platform to check.

I do have the latest platform set.

@snjeza

snjeza commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

I know some errors fail locally because they require running Eclipse with lombok. Errors on the main branch might be caused by some recent updates to the Eclipse 4.40 bits. You can try to reload your target platform to check.

@fbricon @trancexpress You may want to take a look at #3739

@trancexpress

Copy link
Copy Markdown
Author

Thank you @snjeza ! I'll rebase when your PR is merged.

@trancexpress

Copy link
Copy Markdown
Author

I did try cherry-picking f2b053e, locally the tests continue to fail. Maybe it will be better in the Jenkins job.

@trancexpress trancexpress force-pushed the completion-handler-extension branch from 184727b to 2047609 Compare March 18, 2026 15:41
@trancexpress

Copy link
Copy Markdown
Author

Even more fails now:

    org.eclipse.jdt.ls.core.internal.handlers.AdvancedOrganizeImportsHandlerTest.testDuplicateStaticImports
    org.eclipse.jdt.ls.core.internal.handlers.CreateModuleInfoHandlerTest.testCreateModuleInfo
    org.eclipse.jdt.ls.core.internal.handlers.WorkspaceDiagnosticsHandlerTest.testMissingDependencies
    org.eclipse.jdt.ls.core.internal.handlers.WorkspaceSymbolHandlerTest.testEmptyNames
    org.eclipse.jdt.ls.core.internal.managers.ContentProviderManagerTest.testPreferUnknownExtension
    org.eclipse.jdt.ls.core.internal.managers.EclipseProjectImporterTest.testPreviewFeaturesDisabledByDefault
    org.eclipse.jdt.ls.core.internal.managers.InvisibleProjectImporterTest.testPreviewFeaturesEnabledByDefault
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testDownloadSources
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testBuildHelperSupport
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testCompileWithErrorProne
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testDownloadSourcesWhenSHA1SearchFails
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testUpdateSnapshots
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testCompileWithEclipse
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testCompileWithEclipseTychoJdt
    org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupportTest.testUpdate
    org.eclipse.jdt.ls.core.internal.managers.MavenProjectImporterTest.testJava25Project

Running the tests locally from Eclipse, I see only these fails:

org.opentest4j.AssertionFailedError: java25 has errors: 
Type=org.eclipse.jdt.core.problem:Message=Preview features enabled at an invalid source release level 25, preview can be enabled only at source level 26:LineNumber=0 ==> expected: <0> but was: <1>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:563)
	at org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest.assertNoErrors(AbstractProjectsManagerBasedTest.java:401)
	at org.eclipse.jdt.ls.core.internal.managers.EclipseProjectImporterTest.testPreviewFeaturesDisabledByDefault(EclipseProjectImporterTest.java:206)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
org.opentest4j.AssertionFailedError: expected: <enabled> but was: <disabled>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)
	at org.eclipse.jdt.ls.core.internal.managers.InvisibleProjectImporterTest.testPreviewFeaturesEnabledByDefault(InvisibleProjectImporterTest.java:213)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

I'll try with disabling the new test, maybe its corrupting the extension registry somehow...

@fbricon

fbricon commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

new test failures are caused by upstream JDT Core now targeting Java 26 for preview features. #3740 should fix that

@trancexpress trancexpress force-pushed the completion-handler-extension branch from 3eb0be2 to a669cd6 Compare March 18, 2026 19:33
@trancexpress

Copy link
Copy Markdown
Author

Adding the completion handler extension on-the-fly would've been nice, but maybe it causes problems. I've switched to a plain plugin.xml contribution that does nothing during tests (aside from the test for the contribution).

@trancexpress trancexpress force-pushed the completion-handler-extension branch from a669cd6 to c4ad50e Compare March 20, 2026 08:55
@trancexpress

Copy link
Copy Markdown
Author

@fbricon this is ready for a review, tests are green now.

@fbricon fbricon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pondering whether we should allow users to disable specific completion providers via a setting, in case one of them misbehaves and hangs completion entirely.

isIncomplete |= c.isIncomplete();
if (i == 0) {
// TODO: how to merge defaults?
defaults = c.getItemDefaults();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a way to merge the defaults.
I think we should first exclude the itemdefaults not supported by the client
Then just pick the 1st one, so you end up with something like :

if (defaults == null && isClientSupportedItemDefaults(c.getItemDefaults()) {
defaults = c.getItemDefaults();
}

@trancexpress trancexpress Apr 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change adds an extension point
which can be used to contribute a completion handler.

A completion handler can be contributed as follows:

<plugin>
   <extension
      id="contributedCompletionHandler"
      point="org.eclipse.jdt.ls.core.completionHandler">
      <completionHandler
         id="testHandler"
        class="my.test.ContributedCompletionHandler" />
   </extension>
</plugin>

The handler must implement the interface:

org.eclipse.jdt.ls.core.internal.handlers.ICompletionHandler

Offered code completions are the aggregated completions of
the JDT LS CompletionHandler and the contributed completion handlers.

The handler which generated a completion is also the handler
which is asked to complete it, if the completion is selected.
@trancexpress trancexpress force-pushed the completion-handler-extension branch from c4ad50e to 74f70aa Compare April 2, 2026 11:23
@trancexpress

trancexpress commented Apr 2, 2026

Copy link
Copy Markdown
Author

I'm pondering whether we should allow users to disable specific completion providers via a setting, in case one of them misbehaves and hangs completion entirely.

I'm not sure, if this was just Eclipse I would suggest a dialog and a time limit preference, something like "Completion Handler XYZ is unresponsive, disable it?"

When its VS Code on top to show such UIs, I'm not sure how to do that.

If the preference would allow the user to disable a specific provider, the user would need to know what to disable - we would have to tell the user somehow, which provider is being slow.

Maybe this should be up to the maintainers of the product, not including plug-ins that slow down content assist? Resp. to fix that slowness. For maintainers a preference like that would be simple, of course.

Alternatively we can show a preference in VS Code that defaults to all contributed completion provider IDs, the user can then edit and eventually land at the provider that is being slow. Likely jstack would also help for "power" users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants