From f9336287033d81283dfec462c7bb8fb7e9c53524 Mon Sep 17 00:00:00 2001 From: Sebastian Thomschke Date: Sun, 9 Nov 2025 21:29:11 +0100 Subject: [PATCH] fix: advertise callHierarchy client capability Add CallHierarchyCapabilities to SupportedFeatures.getTextDocumentClientCapabilities() so servers can enable callHierarchyProvider (e.g., TypeScript LS requires it). --- .../CallHierarchyViewContentTest.java | 103 ++++++++++++++++++ .../lsp4e/tests/mock/MockLanguageServer.java | 9 ++ .../tests/mock/MockTextDocumentService.java | 43 ++++++++ .../lsp4e/internal/SupportedFeatures.java | 2 + 4 files changed, 157 insertions(+) create mode 100644 org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/callhierarchy/CallHierarchyViewContentTest.java diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/callhierarchy/CallHierarchyViewContentTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/callhierarchy/CallHierarchyViewContentTest.java new file mode 100644 index 000000000..f9b76c58e --- /dev/null +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/callhierarchy/CallHierarchyViewContentTest.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2025 Vegard IT GmbH and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Sebastian Thomschke (Vegard IT GmbH) - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.test.callhierarchy; + +import static org.eclipse.lsp4e.test.utils.TestUtils.waitForAndAssertCondition; +import static org.junit.Assert.*; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.callhierarchy.CallHierarchyContentProvider; +import org.eclipse.lsp4e.callhierarchy.CallHierarchyLabelProvider; +import org.eclipse.lsp4e.callhierarchy.CallHierarchyViewTreeNode; +import org.eclipse.lsp4e.test.utils.AbstractTestWithProject; +import org.eclipse.lsp4e.test.utils.TestUtils; +import org.eclipse.lsp4e.tests.mock.MockLanguageServer; +import org.eclipse.lsp4e.ui.views.HierarchyViewInput; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.junit.Test; + +/** + * UI-level test that opens a file, initializes Call Hierarchy and verifies that + * the view model contains expected nodes. Uses the mock LS. + */ +public class CallHierarchyViewContentTest extends AbstractTestWithProject { + + @Override + public void setUpProject() throws Exception { + super.setUpProject(); + // Ensure the mock server advertises callHierarchyProvider + MockLanguageServer.reset(() -> { + ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities(); + caps.setCallHierarchyProvider(Boolean.TRUE); + return caps; + }); + } + + @Test + public void testCallHierarchyShowsCalleeAndCaller() throws Exception { + IProject p = project; + IFile file = TestUtils.createUniqueTestFile(p, "// mock content for call hierarchy\nfunction f(){}\n"); + + // Open the file in Generic Editor to bind the LS + var editor = TestUtils.openEditor(file); + IDocument document = LSPEclipseUtils.getDocument(editor.getEditorInput()); + assertTrue(document != null); + + // Create a lightweight view embedding a TreeViewer with the real content + // provider + Shell shell = new Shell(editor.getSite().getShell()); + shell.setLayout(new FillLayout()); + TreeViewer viewer = new TreeViewer(shell); + viewer.setContentProvider(new CallHierarchyContentProvider()); + viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new CallHierarchyLabelProvider())); + shell.open(); + + // Initialize input similar to CallHierarchyView.initialize + viewer.setInput(new HierarchyViewInput(document, 0)); + + // Wait until the placeholder ("Finding callers ...") is replaced by a node + waitForAndAssertCondition(5_000, shell.getDisplay(), () -> { + Tree tree = viewer.getTree(); + if (tree.getItemCount() == 0) return false; + Object data = tree.getItem(0).getData(); + return data instanceof CallHierarchyViewTreeNode; + }); + + Tree tree = viewer.getTree(); + TreeItem root = tree.getItem(0); + Object rootData = root.getData(); + assertTrue("Expected CallHierarchyViewTreeNode root", rootData instanceof CallHierarchyViewTreeNode); + var rootNode = (CallHierarchyViewTreeNode) rootData; + assertEquals("callee", rootNode.getCallContainer().getName()); + + // Expand and wait for children + viewer.expandToLevel(2); + waitForAndAssertCondition(5_000, shell.getDisplay(), () -> { + return root.getItemCount() > 0 && root.getItem(0).getData() instanceof CallHierarchyViewTreeNode; + }); + Object childData = root.getItem(0).getData(); + assertTrue(childData instanceof CallHierarchyViewTreeNode); + var childNode = (CallHierarchyViewTreeNode) childData; + assertEquals("caller", childNode.getCallContainer().getName()); + + shell.close(); + } +} diff --git a/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockLanguageServer.java b/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockLanguageServer.java index e68613f90..8fbc06f2a 100644 --- a/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockLanguageServer.java +++ b/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockLanguageServer.java @@ -192,6 +192,15 @@ public static ServerCapabilities defaultServerCapabilities() { @Override public CompletableFuture initialize(InitializeParams params) { + + // Gate callHierarchyProvider on client capability (like real servers do) + var caps = params != null ? params.getCapabilities() : null; + var textDocCaps = caps != null ? caps.getTextDocument() : null; + boolean clientSupportsCallHierarchy = textDocCaps != null && textDocCaps.getCallHierarchy() != null; + if (!clientSupportsCallHierarchy) { + initializeResult.getCapabilities().setCallHierarchyProvider(Either.forLeft(false)); + } + return buildMaybeDelayedFuture(initializeResult); } diff --git a/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockTextDocumentService.java b/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockTextDocumentService.java index 53178604d..3f5d7c1a1 100644 --- a/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockTextDocumentService.java +++ b/org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockTextDocumentService.java @@ -29,6 +29,12 @@ import java.util.stream.Collectors; import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CallHierarchyIncomingCall; +import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams; +import org.eclipse.lsp4j.CallHierarchyItem; +import org.eclipse.lsp4j.CallHierarchyOutgoingCall; +import org.eclipse.lsp4j.CallHierarchyOutgoingCallsParams; +import org.eclipse.lsp4j.CallHierarchyPrepareParams; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.CodeLensParams; @@ -462,6 +468,43 @@ public CompletableFuture semanticTokensFull(SemanticTokensParams private static final Range DUMMY_RANGE = new Range(new Position(0, 0), new Position(0, 0)); + // -------------------------------------- + // Call Hierarchy (minimal mock support) + // -------------------------------------- + @Override + public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { + final String uri = params.getTextDocument().getUri(); + CallHierarchyItem callee = new CallHierarchyItem(); + callee.setName("callee"); + callee.setKind(SymbolKind.Function); + callee.setUri(uri); + callee.setRange(DUMMY_RANGE); + callee.setSelectionRange(DUMMY_RANGE); + return CompletableFuture.completedFuture(List.of(callee)); + } + + @Override + public CompletableFuture> callHierarchyIncomingCalls( + CallHierarchyIncomingCallsParams params) { + CallHierarchyItem caller = new CallHierarchyItem(); + caller.setName("caller"); + caller.setKind(SymbolKind.Function); + caller.setUri(params.getItem().getUri()); + caller.setRange(DUMMY_RANGE); + caller.setSelectionRange(DUMMY_RANGE); + CallHierarchyIncomingCall incoming = new CallHierarchyIncomingCall(); + incoming.setFrom(caller); + incoming.setFromRanges(List.of(DUMMY_RANGE)); + return CompletableFuture.completedFuture(List.of(incoming)); + } + + @Override + public CompletableFuture> callHierarchyOutgoingCalls( + CallHierarchyOutgoingCallsParams params) { + // No outgoing calls in the minimal mock + return CompletableFuture.completedFuture(List.of()); + } + @Override public CompletableFuture> prepareTypeHierarchy(TypeHierarchyPrepareParams params) { return CompletableFuture.completedFuture(List.of(new TypeHierarchyItem("a", SymbolKind.Class, diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java index f5d363a4a..2202a6a0e 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java @@ -14,6 +14,7 @@ import java.util.List; +import org.eclipse.lsp4j.CallHierarchyCapabilities; import org.eclipse.lsp4j.CodeActionCapabilities; import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.CodeActionKindCapabilities; @@ -67,6 +68,7 @@ public class SupportedFeatures { public static TextDocumentClientCapabilities getTextDocumentClientCapabilities() { final var textDocumentClientCapabilities = new TextDocumentClientCapabilities(); + textDocumentClientCapabilities.setCallHierarchy(new CallHierarchyCapabilities()); final var codeAction = new CodeActionCapabilities( new CodeActionLiteralSupportCapabilities(new CodeActionKindCapabilities(List.of( // CodeActionKind.QuickFix, //