diff --git a/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java b/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java index 011e30450..3ea667866 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java @@ -250,6 +250,7 @@ public static ClientCapabilities create(Object experimental) { "documentation", "defaultLibrary"*/ )); + semanticTokensCapabilities.setMultilineTokenSupport(Boolean.TRUE); semanticTokensCapabilities.setServerCancelSupport(Boolean.TRUE); var semanticTokensClientCapabilitiesRequests = new SemanticTokensClientCapabilitiesRequests(Boolean.TRUE, Boolean.FALSE); semanticTokensCapabilities.setFormats(List.of(TokenFormat.Relative)); diff --git a/src/test/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LuaSemanticTokensTest.java b/src/test/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LuaSemanticTokensTest.java new file mode 100644 index 000000000..8db75cd26 --- /dev/null +++ b/src/test/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LuaSemanticTokensTest.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.redhat.devtools.lsp4ij.fixtures.LSPSemanticTokensFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +/** + * Completion tests by emulating LSP 'textDocument/semanticTokens' responses + * from the Lua language server. + */ +public class LuaSemanticTokensTest extends LSPSemanticTokensFixtureTestCase { + + public LuaSemanticTokensTest() { + // Use *.luax instead of *.lua to avoid consuming the lua textmate + super("*.luax"); + } + + + public void testSemanticTokens() { + // 1. Test completion items result + assertSemanticTokens("test.luax", + """ + --[[ + ** {================================================================== + ** Testing memory limits + ** =================================================================== + --]] + + print("memory-allocation errors") + """, + """ + { + "data": [ + 0, + 2, + 4, + 17, + 0, + 6, + 0, + 5, + 12, + 512 + ] + } + """, + """ + --[[ + ** {================================================================== + ** Testing memory limits + ** =================================================================== + --]] + + print("memory-allocation errors") + """ + ); + } + + private void assertSemanticTokens(@NotNull String fileName, + @NotNull String editorContentText, + @NotNull String jsonSemanticTokens, + @NotNull String expected) { + String semanticProvider = """ + { + "legend": { + "tokenTypes": [ + "namespace", + "type", + "class", + "enum", + "interface", + "struct", + "typeParameter", + "parameter", + "variable", + "property", + "enumMember", + "event", + "function", + "method", + "macro", + "keyword", + "modifier", + "comment", + "string", + "number", + "regexp", + "operator", + "decorator" + ], + "tokenModifiers": [ + "declaration", + "definition", + "readonly", + "static", + "deprecated", + "abstract", + "async", + "modification", + "documentation", + "defaultLibrary", + "global" + ] + }, + "range": true, + "full": true + }"""; + assertSemanticTokens(fileName, editorContentText, semanticProvider, jsonSemanticTokens, expected); + } +} diff --git a/src/test/java/com/redhat/devtools/lsp4ij/fixtures/LSPSemanticTokensFixtureTestCase.java b/src/test/java/com/redhat/devtools/lsp4ij/fixtures/LSPSemanticTokensFixtureTestCase.java new file mode 100644 index 000000000..973529a11 --- /dev/null +++ b/src/test/java/com/redhat/devtools/lsp4ij/fixtures/LSPSemanticTokensFixtureTestCase.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.fixtures; + +import com.redhat.devtools.lsp4ij.JSONUtils; +import com.redhat.devtools.lsp4ij.features.semanticTokens.inspector.SemanticTokensInspectorData; +import com.redhat.devtools.lsp4ij.features.semanticTokens.inspector.SemanticTokensInspectorListener; +import com.redhat.devtools.lsp4ij.features.semanticTokens.inspector.SemanticTokensInspectorManager; +import com.redhat.devtools.lsp4ij.mock.MockLanguageServer; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Base class test case to test LSP 'textDocument/semanticTokens' feature. + */ +public abstract class LSPSemanticTokensFixtureTestCase extends LSPCodeInsightFixtureTestCase { + + public LSPSemanticTokensFixtureTestCase(String... fileNamePatterns) { + super(fileNamePatterns); + } + + /** + * Test LSP semanticTokens. + * + * @param fileName the file name used to match registered language servers. + * @param editorContentText the editor content text. + * @param jsonSemanticProvider the LSP SemanticTokensWithRegistrationOptions as JSON string. + * @param jsonSemanticTokens the LSP SemanticTokens as JSON string. + * @param expected the expected IJ SemanticTokens inspector data. + */ + public void assertSemanticTokens(@NotNull String fileName, + @NotNull String editorContentText, + @NotNull String jsonSemanticProvider, + @NotNull String jsonSemanticTokens, + @NotNull String expected) { + var semanticProvider = JSONUtils.getLsp4jGson().fromJson(jsonSemanticProvider, SemanticTokensWithRegistrationOptions.class); + var semanticTokens = JSONUtils.getLsp4jGson().fromJson(jsonSemanticTokens, SemanticTokens.class); + assertSemanticTokens(fileName, editorContentText, semanticProvider, semanticTokens, expected); + } + + /** + * Test LSP semanticTokens. + * + * @param fileName the file name used to match registered language servers. + * @param editorContentText the editor content text. + * @param semanticProvider the LSP SemanticTokensWithRegistrationOptions. + * @param semanticTokens the LSP SemanticTokens. + * @param expected the expected IJ SemanticTokens inspector data. + */ + public void assertSemanticTokens(@NotNull String fileName, + @NotNull String editorContentText, + @NotNull SemanticTokensWithRegistrationOptions semanticProvider, + @NotNull SemanticTokens semanticTokens, + @NotNull String expected) { + + var project = myFixture.getProject(); + var refData = new AtomicReference(); + SemanticTokensInspectorListener listener = data -> refData.set(data); + SemanticTokensInspectorManager.getInstance(project).addSemanticTokensInspectorListener(listener); + try { + + MockLanguageServer.INSTANCE.setTimeToProceedQueries(200); + + var serverCapabilities = MockLanguageServer.INSTANCE.defaultServerCapabilities(); + serverCapabilities.setSemanticTokensProvider(semanticProvider); + MockLanguageServer.reset(() -> serverCapabilities); + MockLanguageServer.INSTANCE.setSemanticTokens(semanticTokens); + + // Open editor for a given file name and content (which declares to know where the completion is triggered). + myFixture.configureByText(fileName, editorContentText); + myFixture.doHighlighting(); + + assertNotNull(refData.get()); + String actual = SemanticTokensInspectorManager.format(refData.get(), true, false, false, project); + assertEquals(expected, actual); + + } + finally { + SemanticTokensInspectorManager.getInstance(project).removeSemanticTokensInspectorListener(listener); + } + } + +} diff --git a/src/test/java/com/redhat/devtools/lsp4ij/mock/MockLanguageServer.java b/src/test/java/com/redhat/devtools/lsp4ij/mock/MockLanguageServer.java index 7db643a8a..0e4863bcf 100644 --- a/src/test/java/com/redhat/devtools/lsp4ij/mock/MockLanguageServer.java +++ b/src/test/java/com/redhat/devtools/lsp4ij/mock/MockLanguageServer.java @@ -162,6 +162,10 @@ public void setCompletionList(CompletionList completionList) { this.textDocumentService.setMockCompletionList(completionList); } + public void setSemanticTokens(SemanticTokens semanticTokens) { + this.textDocumentService.setSemanticTokens(semanticTokens); + } + public void setHover(Hover hover) { this.textDocumentService.setMockHover(hover); } @@ -320,4 +324,5 @@ public String toString() { return "MockLanguageServer [started=" + started + ", delay=" + delay + ", remoteProxies=" + remoteProxies.size() + ", inFlight=" + inFlight.size() + "]"; } + } \ No newline at end of file diff --git a/src/test/java/com/redhat/devtools/lsp4ij/mock/MockTextDocumentService.java b/src/test/java/com/redhat/devtools/lsp4ij/mock/MockTextDocumentService.java index 6fb5c3b40..60406c3f9 100644 --- a/src/test/java/com/redhat/devtools/lsp4ij/mock/MockTextDocumentService.java +++ b/src/test/java/com/redhat/devtools/lsp4ij/mock/MockTextDocumentService.java @@ -152,7 +152,7 @@ public CompletableFuture> codeLens(CodeLensParams param File file = new File(URI.create(params.getTextDocument().getUri())); if (file.exists() && file.length() > 100) { return CompletableFuture.completedFuture(Collections.singletonList(new CodeLens( - new Range(new Position(1, 0), new Position(1, 1)), new Command("Hi, I'm a CodeLens", null), null))); + new Range(new Position(1, 0), new Position(1, 1)), new Command("Hi, I'm a CodeLens", "mock.command"), null))); } return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -410,4 +410,5 @@ public void setFoldingRanges(List foldingRanges) { public CompletableFuture> foldingRange(FoldingRangeRequestParams params) { return CompletableFuture.completedFuture(this.foldingRanges); } + } \ No newline at end of file