Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ public static ServerCapabilities defaultServerCapabilities() {

@Override
public CompletableFuture<InitializeResult> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -462,6 +468,43 @@ public CompletableFuture<SemanticTokens> 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<List<CallHierarchyItem>> 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<List<CallHierarchyIncomingCall>> 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<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(
CallHierarchyOutgoingCallsParams params) {
// No outgoing calls in the minimal mock
return CompletableFuture.completedFuture(List.of());
}

@Override
public CompletableFuture<List<TypeHierarchyItem>> prepareTypeHierarchy(TypeHierarchyPrepareParams params) {
return CompletableFuture.completedFuture(List.of(new TypeHierarchyItem("a", SymbolKind.Class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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, //
Expand Down