Skip to content

Commit 589e1cc

Browse files
sebthomrubenporras
authored andcommitted
fix: advertise callHierarchy client capability
Add CallHierarchyCapabilities to SupportedFeatures.getTextDocumentClientCapabilities() so servers can enable callHierarchyProvider (e.g., TypeScript LS requires it).
1 parent 4ec874a commit 589e1cc

4 files changed

Lines changed: 157 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vegard IT GmbH and others.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Sebastian Thomschke (Vegard IT GmbH) - initial implementation
11+
*******************************************************************************/
12+
package org.eclipse.lsp4e.test.callhierarchy;
13+
14+
import static org.eclipse.lsp4e.test.utils.TestUtils.waitForAndAssertCondition;
15+
import static org.junit.Assert.*;
16+
17+
import org.eclipse.core.resources.IFile;
18+
import org.eclipse.core.resources.IProject;
19+
import org.eclipse.jface.text.IDocument;
20+
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
21+
import org.eclipse.jface.viewers.TreeViewer;
22+
import org.eclipse.lsp4e.LSPEclipseUtils;
23+
import org.eclipse.lsp4e.callhierarchy.CallHierarchyContentProvider;
24+
import org.eclipse.lsp4e.callhierarchy.CallHierarchyLabelProvider;
25+
import org.eclipse.lsp4e.callhierarchy.CallHierarchyViewTreeNode;
26+
import org.eclipse.lsp4e.test.utils.AbstractTestWithProject;
27+
import org.eclipse.lsp4e.test.utils.TestUtils;
28+
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
29+
import org.eclipse.lsp4e.ui.views.HierarchyViewInput;
30+
import org.eclipse.lsp4j.ServerCapabilities;
31+
import org.eclipse.swt.layout.FillLayout;
32+
import org.eclipse.swt.widgets.Shell;
33+
import org.eclipse.swt.widgets.Tree;
34+
import org.eclipse.swt.widgets.TreeItem;
35+
import org.junit.Test;
36+
37+
/**
38+
* UI-level test that opens a file, initializes Call Hierarchy and verifies that
39+
* the view model contains expected nodes. Uses the mock LS.
40+
*/
41+
public class CallHierarchyViewContentTest extends AbstractTestWithProject {
42+
43+
@Override
44+
public void setUpProject() throws Exception {
45+
super.setUpProject();
46+
// Ensure the mock server advertises callHierarchyProvider
47+
MockLanguageServer.reset(() -> {
48+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
49+
caps.setCallHierarchyProvider(Boolean.TRUE);
50+
return caps;
51+
});
52+
}
53+
54+
@Test
55+
public void testCallHierarchyShowsCalleeAndCaller() throws Exception {
56+
IProject p = project;
57+
IFile file = TestUtils.createUniqueTestFile(p, "// mock content for call hierarchy\nfunction f(){}\n");
58+
59+
// Open the file in Generic Editor to bind the LS
60+
var editor = TestUtils.openEditor(file);
61+
IDocument document = LSPEclipseUtils.getDocument(editor.getEditorInput());
62+
assertTrue(document != null);
63+
64+
// Create a lightweight view embedding a TreeViewer with the real content
65+
// provider
66+
Shell shell = new Shell(editor.getSite().getShell());
67+
shell.setLayout(new FillLayout());
68+
TreeViewer viewer = new TreeViewer(shell);
69+
viewer.setContentProvider(new CallHierarchyContentProvider());
70+
viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new CallHierarchyLabelProvider()));
71+
shell.open();
72+
73+
// Initialize input similar to CallHierarchyView.initialize
74+
viewer.setInput(new HierarchyViewInput(document, 0));
75+
76+
// Wait until the placeholder ("Finding callers ...") is replaced by a node
77+
waitForAndAssertCondition(5_000, shell.getDisplay(), () -> {
78+
Tree tree = viewer.getTree();
79+
if (tree.getItemCount() == 0) return false;
80+
Object data = tree.getItem(0).getData();
81+
return data instanceof CallHierarchyViewTreeNode;
82+
});
83+
84+
Tree tree = viewer.getTree();
85+
TreeItem root = tree.getItem(0);
86+
Object rootData = root.getData();
87+
assertTrue("Expected CallHierarchyViewTreeNode root", rootData instanceof CallHierarchyViewTreeNode);
88+
var rootNode = (CallHierarchyViewTreeNode) rootData;
89+
assertEquals("callee", rootNode.getCallContainer().getName());
90+
91+
// Expand and wait for children
92+
viewer.expandToLevel(2);
93+
waitForAndAssertCondition(5_000, shell.getDisplay(), () -> {
94+
return root.getItemCount() > 0 && root.getItem(0).getData() instanceof CallHierarchyViewTreeNode;
95+
});
96+
Object childData = root.getItem(0).getData();
97+
assertTrue(childData instanceof CallHierarchyViewTreeNode);
98+
var childNode = (CallHierarchyViewTreeNode) childData;
99+
assertEquals("caller", childNode.getCallContainer().getName());
100+
101+
shell.close();
102+
}
103+
}

org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockLanguageServer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,15 @@ public static ServerCapabilities defaultServerCapabilities() {
192192

193193
@Override
194194
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
195+
196+
// Gate callHierarchyProvider on client capability (like real servers do)
197+
var caps = params != null ? params.getCapabilities() : null;
198+
var textDocCaps = caps != null ? caps.getTextDocument() : null;
199+
boolean clientSupportsCallHierarchy = textDocCaps != null && textDocCaps.getCallHierarchy() != null;
200+
if (!clientSupportsCallHierarchy) {
201+
initializeResult.getCapabilities().setCallHierarchyProvider(Either.forLeft(false));
202+
}
203+
195204
return buildMaybeDelayedFuture(initializeResult);
196205
}
197206

org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockTextDocumentService.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
import java.util.stream.Collectors;
3030

3131
import org.eclipse.lsp4j.CodeAction;
32+
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
33+
import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams;
34+
import org.eclipse.lsp4j.CallHierarchyItem;
35+
import org.eclipse.lsp4j.CallHierarchyOutgoingCall;
36+
import org.eclipse.lsp4j.CallHierarchyOutgoingCallsParams;
37+
import org.eclipse.lsp4j.CallHierarchyPrepareParams;
3238
import org.eclipse.lsp4j.CodeActionParams;
3339
import org.eclipse.lsp4j.CodeLens;
3440
import org.eclipse.lsp4j.CodeLensParams;
@@ -462,6 +468,43 @@ public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams
462468

463469
private static final Range DUMMY_RANGE = new Range(new Position(0, 0), new Position(0, 0));
464470

471+
// --------------------------------------
472+
// Call Hierarchy (minimal mock support)
473+
// --------------------------------------
474+
@Override
475+
public CompletableFuture<List<CallHierarchyItem>> prepareCallHierarchy(CallHierarchyPrepareParams params) {
476+
final String uri = params.getTextDocument().getUri();
477+
CallHierarchyItem callee = new CallHierarchyItem();
478+
callee.setName("callee");
479+
callee.setKind(SymbolKind.Function);
480+
callee.setUri(uri);
481+
callee.setRange(DUMMY_RANGE);
482+
callee.setSelectionRange(DUMMY_RANGE);
483+
return CompletableFuture.completedFuture(List.of(callee));
484+
}
485+
486+
@Override
487+
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(
488+
CallHierarchyIncomingCallsParams params) {
489+
CallHierarchyItem caller = new CallHierarchyItem();
490+
caller.setName("caller");
491+
caller.setKind(SymbolKind.Function);
492+
caller.setUri(params.getItem().getUri());
493+
caller.setRange(DUMMY_RANGE);
494+
caller.setSelectionRange(DUMMY_RANGE);
495+
CallHierarchyIncomingCall incoming = new CallHierarchyIncomingCall();
496+
incoming.setFrom(caller);
497+
incoming.setFromRanges(List.of(DUMMY_RANGE));
498+
return CompletableFuture.completedFuture(List.of(incoming));
499+
}
500+
501+
@Override
502+
public CompletableFuture<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(
503+
CallHierarchyOutgoingCallsParams params) {
504+
// No outgoing calls in the minimal mock
505+
return CompletableFuture.completedFuture(List.of());
506+
}
507+
465508
@Override
466509
public CompletableFuture<List<TypeHierarchyItem>> prepareTypeHierarchy(TypeHierarchyPrepareParams params) {
467510
return CompletableFuture.completedFuture(List.of(new TypeHierarchyItem("a", SymbolKind.Class,

org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import java.util.List;
1616

17+
import org.eclipse.lsp4j.CallHierarchyCapabilities;
1718
import org.eclipse.lsp4j.CodeActionCapabilities;
1819
import org.eclipse.lsp4j.CodeActionKind;
1920
import org.eclipse.lsp4j.CodeActionKindCapabilities;
@@ -67,6 +68,7 @@ public class SupportedFeatures {
6768

6869
public static TextDocumentClientCapabilities getTextDocumentClientCapabilities() {
6970
final var textDocumentClientCapabilities = new TextDocumentClientCapabilities();
71+
textDocumentClientCapabilities.setCallHierarchy(new CallHierarchyCapabilities());
7072
final var codeAction = new CodeActionCapabilities(
7173
new CodeActionLiteralSupportCapabilities(new CodeActionKindCapabilities(List.of( //
7274
CodeActionKind.QuickFix, //

0 commit comments

Comments
 (0)