Skip to content

Commit a65c4f8

Browse files
committed
Add LSP Implementation infra. AOT query method impl nav.
1 parent 21b2683 commit a65c4f8

File tree

31 files changed

+251
-107
lines changed

31 files changed

+251
-107
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.commons.languageserver.util;
12+
13+
import java.util.List;
14+
15+
import org.eclipse.lsp4j.ImplementationParams;
16+
import org.eclipse.lsp4j.LocationLink;
17+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
18+
19+
@FunctionalInterface
20+
public interface ImplementationHandler {
21+
List<LocationLink> handle(CancelChecker cancelToken, ImplementationParams implParams);
22+
}

headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,9 @@ protected final ServerCapabilities getServerCapabilities() {
513513
if (hasDefinitionHandler()) {
514514
c.setDefinitionProvider(true);
515515
}
516+
if (hasImplementationHandler()) {
517+
c.setImplementationProvider(true);
518+
}
516519
if (hasReferencesHandler()) {
517520
c.setReferencesProvider(true);
518521
}
@@ -598,6 +601,10 @@ private boolean hasReferencesHandler() {
598601
private boolean hasDefinitionHandler() {
599602
return getTextDocumentService().hasDefinitionHandler();
600603
}
604+
605+
private boolean hasImplementationHandler() {
606+
return getTextDocumentService().hasImplementationHandler();
607+
}
601608

602609
private boolean hasQuickFixes() {
603610
return quickfixRegistry!=null && quickfixRegistry.hasFixes();

headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.eclipse.lsp4j.DocumentSymbolParams;
5353
import org.eclipse.lsp4j.Hover;
5454
import org.eclipse.lsp4j.HoverParams;
55+
import org.eclipse.lsp4j.ImplementationParams;
5556
import org.eclipse.lsp4j.InlayHint;
5657
import org.eclipse.lsp4j.InlayHintParams;
5758
import org.eclipse.lsp4j.Location;
@@ -120,6 +121,7 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
120121
private CompletionResolveHandler completionResolveHandler;
121122
private HoverHandler hoverHandler;
122123
private DefinitionHandler definitionHandler;
124+
private ImplementationHandler implementationHandler;
123125
private ReferencesHandler referencesHandler;
124126
private DocumentSymbolHandler documentSymbolHandler;
125127
private DocumentHighlightHandler documentHighlightHandler;
@@ -401,6 +403,38 @@ public CompletableFuture<Either<List<? extends Location>, List<? extends Locatio
401403
}
402404
}
403405

406+
407+
@Override
408+
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> implementation(
409+
ImplementationParams implemetationParams) {
410+
ImplementationHandler h = this.implementationHandler;
411+
if (h != null) {
412+
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> {
413+
414+
cancelToken.checkCanceled();
415+
416+
List<LocationLink> locations = h.handle(cancelToken, implemetationParams);
417+
if (locations == null) {
418+
// vscode client does not like to receive null result. See: https://github.com/spring-projects/sts4/issues/309
419+
locations = ImmutableList.of();
420+
}
421+
// Workaround for https://github.com/eclipse-theia/theia/issues/6414
422+
// Theia does not support LocationLink yet
423+
switch (LspClient.currentClient()) {
424+
case THEIA:
425+
case ATOM:
426+
case INTELLIJ:
427+
return Either.forLeft(locations.stream().map(link -> new Location(link.getTargetUri(), link.getTargetRange())).collect(Collectors.toList()));
428+
default:
429+
return Either.forRight(locations);
430+
}
431+
});
432+
}
433+
else {
434+
return CompletableFuture.completedFuture(Either.forLeft(ImmutableList.of()));
435+
}
436+
}
437+
404438
@Override
405439
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
406440
ReferencesHandler h = this.referencesHandler;
@@ -783,10 +817,19 @@ public synchronized void onDefinition(DefinitionHandler h) {
783817
Assert.isNull("A defintion handler is already set, multiple handlers not supported yet", definitionHandler);
784818
this.definitionHandler = h;
785819
}
820+
821+
public synchronized void onImplementation(ImplementationHandler h) {
822+
Assert.isNull("An implementation handler is already set, multiple handlers not supported yet", implementationHandler);
823+
this.implementationHandler = h;
824+
}
786825

787826
public boolean hasDefinitionHandler() {
788827
return definitionHandler != null;
789828
}
829+
830+
public boolean hasImplementationHandler() {
831+
return implementationHandler != null;
832+
}
790833

791834
public synchronized void onReferences(ReferencesHandler h) {
792835
Assert.isNull("A references handler is already set, multiple handlers not supported yet", referencesHandler);

headless-services/commons/language-server-starter/src/main/java/org/springframework/ide/vscode/languageserver/starter/LanguageServerAutoConf.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018, 2021 Pivotal, Inc.
2+
* Copyright (c) 2018, 2025 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -28,6 +28,7 @@
2828
import org.springframework.ide.vscode.commons.languageserver.util.CompletionServerCapabilityRegistration;
2929
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
3030
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
31+
import org.springframework.ide.vscode.commons.languageserver.util.ImplementationHandler;
3132
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
3233
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
3334
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -93,6 +94,38 @@ InitializingBean registerDefinitionHandler(SimpleTextDocumentService documents,
9394
}
9495
}
9596

97+
@ConditionalOnBean(ImplementationHandler.class)
98+
@Bean
99+
InitializingBean registerImplementationHandler(SimpleTextDocumentService documents,
100+
List<ImplementationHandler> implHandlers) {
101+
if (implHandlers.size() == 1) {
102+
return () -> documents.onImplementation(implHandlers.get(0));
103+
} else {
104+
Map<LanguageId, ImplementationHandler> handlers = new HashMap<>(implHandlers.size());
105+
for (ImplementationHandler h : implHandlers) {
106+
Assert.isInstanceOf(LanguageSpecific.class, h, "Only language specific defintion handlers supported!");
107+
for (LanguageId l : ((LanguageSpecific)h).supportedLanguages()) {
108+
Assert.isTrue(!handlers.containsKey(l), "Multiple definition handlers for the same language not supported!");
109+
handlers.put(l, h);
110+
}
111+
}
112+
113+
ImmutableMap<LanguageId, ImplementationHandler> immutableMap = ImmutableMap.copyOf(handlers);
114+
return () -> documents.onImplementation((cancelToken, position) -> {
115+
TextDocument doc = documents.getLatestSnapshot(position);
116+
117+
if (doc != null) {
118+
LanguageId language = doc.getLanguageId();
119+
ImplementationHandler handler = immutableMap.get(language);
120+
if (handler != null) {
121+
return handler.handle(cancelToken, position);
122+
}
123+
}
124+
return null;
125+
});
126+
}
127+
}
128+
96129
@ConditionalOnBean(DocumentSymbolHandler.class)
97130
@Bean
98131
InitializingBean registerDocumentSymbolHandler(SimpleTextDocumentService documents, DocumentSymbolHandler handler) {

headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.eclipse.lsp4j.DocumentHighlight;
4545
import org.eclipse.lsp4j.DocumentSymbol;
4646
import org.eclipse.lsp4j.Hover;
47+
import org.eclipse.lsp4j.ImplementationParams;
4748
import org.eclipse.lsp4j.InsertReplaceEdit;
4849
import org.eclipse.lsp4j.Location;
4950
import org.eclipse.lsp4j.LocationLink;
@@ -864,24 +865,39 @@ public String toString() {
864865
return "Editor(\n" + getText() + "\n)";
865866
}
866867

867-
public void assertLinkTargets(String hoverOver, Collection<LocationLink> expectedLocations) throws Exception {
868+
public void assertDefinitionLinkTargets(String hoverOver, Collection<LocationLink> expectedLocations) throws Exception {
868869
int pos = getRawText().indexOf(hoverOver);
869870
if (pos >= 0) {
870871
pos += hoverOver.length() / 2;
871872
}
872873
assertTrue(pos>=0, "Not found in editor: '"+hoverOver+"'");
873874

874-
assertLinkTargets(doc.toPosition(pos), expectedLocations);
875+
assertDefinitionLinkTargets(doc.toPosition(pos), expectedLocations);
875876
}
876877

877-
public void assertLinkTargets(Position pos, Collection<LocationLink> expectedLocations) throws Exception {
878+
public void assertDefinitionLinkTargets(Position pos, Collection<LocationLink> expectedLocations) throws Exception {
878879
DefinitionParams params = new DefinitionParams(new TextDocumentIdentifier(getUri()), pos);
879880
List<? extends LocationLink> definitions = harness.getDefinitions(params);
880881
assertEquals(ImmutableSet.copyOf(expectedLocations), ImmutableSet.copyOf(definitions));
881882
}
883+
884+
public void assertImplementationLinkTargets(String hoverOver, Collection<LocationLink> expectedLocations) throws Exception {
885+
int pos = getRawText().indexOf(hoverOver);
886+
if (pos >= 0) {
887+
pos += hoverOver.length() / 2;
888+
}
889+
assertTrue(pos>=0, "Not found in editor: '"+hoverOver+"'");
890+
891+
assertImplementationLinkTargets(doc.toPosition(pos), expectedLocations);
892+
}
882893

894+
public void assertImplementationLinkTargets(Position pos, Collection<LocationLink> expectedLocations) throws Exception {
895+
ImplementationParams params = new ImplementationParams(new TextDocumentIdentifier(getUri()), pos);
896+
List<? extends LocationLink> definitions = harness.getImplemetations(params);
897+
assertEquals(ImmutableSet.copyOf(expectedLocations), ImmutableSet.copyOf(definitions));
898+
}
883899

884-
public void assertNoLinkTargets(String hoverOver) throws Exception {
900+
public void assertNoDefinitionLinkTargets(String hoverOver) throws Exception {
885901
int pos = getRawText().indexOf(hoverOver);
886902
if (pos >= 0) {
887903
pos += hoverOver.length() / 2;

headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import org.eclipse.lsp4j.FileEvent;
7878
import org.eclipse.lsp4j.Hover;
7979
import org.eclipse.lsp4j.HoverParams;
80+
import org.eclipse.lsp4j.ImplementationParams;
8081
import org.eclipse.lsp4j.InitializeParams;
8182
import org.eclipse.lsp4j.InitializeResult;
8283
import org.eclipse.lsp4j.InlayHint;
@@ -825,6 +826,11 @@ public List<? extends LocationLink> getDefinitions(DefinitionParams params) thro
825826
return getServer().getTextDocumentService().definition(params).get().getRight();
826827
}
827828

829+
public List<? extends LocationLink> getImplemetations(ImplementationParams params) throws Exception {
830+
waitForReconcile(); //goto definitions relies on reconciler infos! Must wait or race condition breaking tests occasionally.
831+
return getServer().getTextDocumentService().implementation(params).get().getRight();
832+
}
833+
828834
public static void assertDocumentation(String expected, CompletionItem completion) {
829835
assertEquals(expected, getDocString(completion));
830836
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnResourceDefinitionProvider;
5555
import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier;
5656
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
57-
import org.springframework.ide.vscode.boot.java.data.GenAotQueryMethodDefinitionProvider;
57+
import org.springframework.ide.vscode.boot.java.data.GenAotQueryMethodImplProvider;
5858
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
5959
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
6060
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
@@ -419,8 +419,11 @@ JavaDefinitionHandler javaDefinitionHandler(SimpleLanguageServer server, Compila
419419
new QualifierDefinitionProvider(springIndex),
420420
new NamedDefinitionProvider(springIndex),
421421
new DataQueryParameterDefinitionProvider(server.getTextDocumentService(), qurySemanticTokens),
422-
new SpelDefinitionProvider(springIndex, cuCache),
423-
new GenAotQueryMethodDefinitionProvider(server, cuCache, projectFinder)));
422+
new SpelDefinitionProvider(springIndex, cuCache)
423+
),
424+
List.of(
425+
new GenAotQueryMethodImplProvider(server, cuCache, projectFinder)
426+
));
424427
}
425428

426429
@Bean
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
2020
import org.springframework.ide.vscode.commons.java.IJavaProject;
2121

22-
public interface IJavaDefinitionProvider {
22+
public interface IJavaLocationLinksProvider {
2323

24-
List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset);
24+
List<LocationLink> getLocationLinks(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset);
2525

2626
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/JavaDefinitionHandler.java

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023 VMware, Inc.
2+
* Copyright (c) 2023, 2025 VMware, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -18,34 +18,40 @@
1818
import org.eclipse.jdt.core.dom.ASTNode;
1919
import org.eclipse.jdt.core.dom.NodeFinder;
2020
import org.eclipse.lsp4j.DefinitionParams;
21+
import org.eclipse.lsp4j.ImplementationParams;
2122
import org.eclipse.lsp4j.LocationLink;
2223
import org.eclipse.lsp4j.TextDocumentIdentifier;
24+
import org.eclipse.lsp4j.TextDocumentPositionAndWorkDoneProgressAndPartialResultParams;
2325
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
2426
import org.slf4j.Logger;
2527
import org.slf4j.LoggerFactory;
2628
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
2729
import org.springframework.ide.vscode.commons.java.IJavaProject;
2830
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
2931
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
32+
import org.springframework.ide.vscode.commons.languageserver.util.ImplementationHandler;
3033
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
3134
import org.springframework.ide.vscode.commons.util.text.LanguageId;
3235

3336
import com.google.common.collect.ImmutableList;
3437
import com.google.common.collect.ImmutableList.Builder;
3538

36-
public class JavaDefinitionHandler implements DefinitionHandler, LanguageSpecific {
39+
public class JavaDefinitionHandler implements DefinitionHandler, ImplementationHandler, LanguageSpecific {
3740

3841
private static final Logger log = LoggerFactory.getLogger(JavaDefinitionHandler.class);
3942

40-
private CompilationUnitCache cuCache;
41-
private JavaProjectFinder projectFinder;
42-
private Collection<IJavaDefinitionProvider> providers;
43+
private final CompilationUnitCache cuCache;
44+
private final JavaProjectFinder projectFinder;
45+
private final Collection<IJavaLocationLinksProvider> defProviders;
46+
private final Collection<IJavaLocationLinksProvider> implProviders;
4347

4448
public JavaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder,
45-
Collection<IJavaDefinitionProvider> providers) {
49+
Collection<IJavaLocationLinksProvider> defProviders,
50+
Collection<IJavaLocationLinksProvider> implProviders) {
4651
this.cuCache = cuCache;
4752
this.projectFinder = projectFinder;
48-
this.providers = providers;
53+
this.defProviders = defProviders;
54+
this.implProviders = implProviders;
4955
}
5056

5157
@Override
@@ -55,21 +61,30 @@ public Collection<LanguageId> supportedLanguages() {
5561

5662
@Override
5763
public List<LocationLink> handle(CancelChecker cancelToken, DefinitionParams definitionParams) {
58-
TextDocumentIdentifier doc = definitionParams.getTextDocument();
64+
return findLinks(cancelToken, defProviders, definitionParams);
65+
}
66+
67+
@Override
68+
public List<LocationLink> handle(CancelChecker cancelToken, ImplementationParams implParams) {
69+
return findLinks(cancelToken, implProviders, implParams);
70+
}
71+
72+
private List<LocationLink> findLinks(CancelChecker cancelToken, Collection<IJavaLocationLinksProvider> providers, TextDocumentPositionAndWorkDoneProgressAndPartialResultParams params) {
73+
TextDocumentIdentifier doc = params.getTextDocument();
5974
IJavaProject project = projectFinder.find(doc).orElse(null);
6075
if (project != null) {
6176
URI docUri = URI.create(doc.getUri());
6277
return cuCache.withCompilationUnit(project, docUri, cu -> {
6378
Builder<LocationLink> builder = ImmutableList.builder();
6479
if (cu != null) {
65-
int start = cu.getPosition(definitionParams.getPosition().getLine() + 1, definitionParams.getPosition().getCharacter());
80+
int start = cu.getPosition(params.getPosition().getLine() + 1, params.getPosition().getCharacter());
6681
ASTNode node = NodeFinder.perform(cu, start, 0);
67-
for (IJavaDefinitionProvider provider : providers) {
82+
for (IJavaLocationLinksProvider provider : providers) {
6883
if (cancelToken.isCanceled()) {
6984
break;
7085
}
7186
try {
72-
builder.addAll(provider.getDefinitions(cancelToken, project, doc, cu, node, start));
87+
builder.addAll(provider.getLocationLinks(cancelToken, project, doc, cu, node, start));
7388
} catch (Exception e) {
7489
log.error("", e);
7590
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/DependsOnDefinitionProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@
2525
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
2626
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
2727
import org.springframework.ide.vscode.boot.java.Annotations;
28-
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
28+
import org.springframework.ide.vscode.boot.java.IJavaLocationLinksProvider;
2929
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
3030
import org.springframework.ide.vscode.commons.java.IJavaProject;
3131
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
3232

3333
/**
3434
* @author Martin Lippert
3535
*/
36-
public class DependsOnDefinitionProvider implements IJavaDefinitionProvider {
36+
public class DependsOnDefinitionProvider implements IJavaLocationLinksProvider {
3737

3838
private final SpringMetamodelIndex springIndex;
3939

@@ -42,7 +42,7 @@ public DependsOnDefinitionProvider(SpringMetamodelIndex springIndex) {
4242
}
4343

4444
@Override
45-
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
45+
public List<LocationLink> getLocationLinks(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
4646
if (n instanceof StringLiteral) {
4747
StringLiteral valueNode = (StringLiteral) n;
4848

0 commit comments

Comments
 (0)