diff --git a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF index c70633892..7a360a319 100644 --- a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF +++ b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Tests for language server bundle (Incubation) Bundle-SymbolicName: org.eclipse.lsp4e.test;singleton:=true -Bundle-Version: 0.16.1.qualifier +Bundle-Version: 0.16.2.qualifier Fragment-Host: org.eclipse.lsp4e Bundle-Vendor: Eclipse LSP4E Bundle-RequiredExecutionEnvironment: JavaSE-21 diff --git a/org.eclipse.lsp4e.test/pom.xml b/org.eclipse.lsp4e.test/pom.xml index ef54b04de..349e83c39 100644 --- a/org.eclipse.lsp4e.test/pom.xml +++ b/org.eclipse.lsp4e.test/pom.xml @@ -8,7 +8,7 @@ org.eclipse.lsp4e.test eclipse-test-plugin - 0.16.1-SNAPSHOT + 0.16.2-SNAPSHOT diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/commands/DynamicRegistrationTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/commands/DynamicRegistrationTest.java index d89ff7141..af2e90765 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/commands/DynamicRegistrationTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/commands/DynamicRegistrationTest.java @@ -45,8 +45,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.google.gson.Gson; - public class DynamicRegistrationTest extends AbstractTestWithProject { private static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand"; @@ -143,7 +141,7 @@ private UUID registerWatchedFiles() throws Exception { final var watcher = new org.eclipse.lsp4j.FileSystemWatcher( org.eclipse.lsp4j.jsonrpc.messages.Either.forLeft("**/*.txt"), null); options.setWatchers(List.of(watcher)); - registration.setRegisterOptions(new Gson().toJsonTree(options)); + registration.setRegisterOptions(options); client.registerCapability(new RegistrationParams(List.of(registration))).get(1, TimeUnit.SECONDS); return id; } @@ -165,7 +163,7 @@ private UUID registerCommands(String... command) throws Exception { final var registration = new Registration(); registration.setId(id.toString()); registration.setMethod(WORKSPACE_EXECUTE_COMMAND); - registration.setRegisterOptions(new Gson().toJsonTree(new ExecuteCommandOptions(List.of(command)))); + registration.setRegisterOptions(new ExecuteCommandOptions(List.of(command))); client.registerCapability(new RegistrationParams(List.of(registration))).get(1, TimeUnit.SECONDS); return id; } diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/DynamicCompletionRegistrationTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/DynamicCompletionRegistrationTest.java index 08f544b24..a751b94e1 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/DynamicCompletionRegistrationTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/DynamicCompletionRegistrationTest.java @@ -41,8 +41,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.google.gson.Gson; - /** * Verifies that dynamic registration of completion updates LSP4E server * capabilities and enables content assist proposals. @@ -82,7 +80,7 @@ public void testDynamicCompletionRegistrationProvidesProposalsAndTriggers() thro registration.setMethod("textDocument/completion"); var opts = new CompletionOptions(); opts.setTriggerCharacters(List.of(".", "/", "#")); - registration.setRegisterOptions(new Gson().toJsonTree(opts)); + registration.setRegisterOptions(opts); client.registerCapability(new RegistrationParams(List.of(registration))).get(2, TimeUnit.SECONDS); // Compute proposals (manual invocation). Should return the mock item. diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/internal/JsonUtilTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/internal/JsonUtilTest.java new file mode 100644 index 000000000..22f2f400c --- /dev/null +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/internal/JsonUtilTest.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. + * 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: + * See git history + *******************************************************************************/ +package org.eclipse.lsp4e.test.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.lsp4e.internal.JsonUtil; +import org.eclipse.lsp4j.FileSystemWatcher; +import org.eclipse.lsp4j.WatchKind; +import org.junit.jupiter.api.Test; + +public class JsonUtilTest { + + @Test + void testRoundtrip() throws Exception { + // Setup an object which uses Either internally. + // This can only be properly de/serialized with Gson instance which knows about LSP4J types. + var original = new FileSystemWatcher(); + original.setGlobPattern("**"); + original.setKind(WatchKind.Create | WatchKind.Change | WatchKind.Delete); + + String json = JsonUtil.LSP4J_GSON.toJson(original); + assertEquals(""" + {"globPattern":"**","kind":7}""", json); + + FileSystemWatcher copy = JsonUtil.LSP4J_GSON.fromJson(json, FileSystemWatcher.class); + assertEquals(original, copy); + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java index de24efc8b..f0430de08 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java @@ -18,7 +18,7 @@ *******************************************************************************/ package org.eclipse.lsp4e; -import static org.eclipse.lsp4e.internal.NullSafetyHelper.*; +import static org.eclipse.lsp4e.internal.NullSafetyHelper.castNonNull; import java.io.BufferedReader; import java.io.File; @@ -80,6 +80,7 @@ import org.eclipse.lsp4e.internal.ArrayUtil; import org.eclipse.lsp4e.internal.CancellationUtil; import org.eclipse.lsp4e.internal.FileBufferListenerAdapter; +import org.eclipse.lsp4e.internal.JsonUtil; import org.eclipse.lsp4e.internal.SupportedFeatures; import org.eclipse.lsp4e.internal.files.FileSystemWatcherManager; import org.eclipse.lsp4e.server.StreamConnectionProvider; @@ -129,7 +130,6 @@ import com.google.common.base.Functions; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.gson.Gson; import com.google.gson.JsonObject; public class LanguageServerWrapper { @@ -1173,7 +1173,7 @@ public void registerCapability(RegistrationParams params) { break; case "workspace/executeCommand": //$NON-NLS-1$ try { - ExecuteCommandOptions executeCommandOptions = castNonNull(new Gson().fromJson((JsonObject) reg.getRegisterOptions(), + ExecuteCommandOptions executeCommandOptions = castNonNull(JsonUtil.LSP4J_GSON.fromJson((JsonObject) reg.getRegisterOptions(), ExecuteCommandOptions.class)); List newCommands = executeCommandOptions.getCommands(); if (!newCommands.isEmpty()) { @@ -1212,7 +1212,7 @@ public void registerCapability(RegistrationParams params) { case "textDocument/completion": { //$NON-NLS-1$ CompletionOptions previous = serverCapabilities.getCompletionProvider(); try { - final var completionOpts = new Gson().fromJson((JsonObject) reg.getRegisterOptions(), + final var completionOpts = JsonUtil.LSP4J_GSON.fromJson((JsonObject) reg.getRegisterOptions(), CompletionOptions.class); serverCapabilities.setCompletionProvider(completionOpts); addRegistration(reg, () -> serverCapabilities.setCompletionProvider(previous)); @@ -1255,8 +1255,9 @@ public void registerCapability(RegistrationParams params) { return null; if (registerOptions instanceof DidChangeWatchedFilesRegistrationOptions direct) return direct; - if (registerOptions instanceof JsonObject jsonObject) - return new Gson().fromJson(jsonObject, DidChangeWatchedFilesRegistrationOptions.class); + if (registerOptions instanceof JsonObject jsonObject) { + return JsonUtil.LSP4J_GSON.fromJson(jsonObject, DidChangeWatchedFilesRegistrationOptions.class); + } return null; } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/command/CommandExecutor.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/command/CommandExecutor.java index 3ff684bda..c8414a1d5 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/command/CommandExecutor.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/command/CommandExecutor.java @@ -8,7 +8,8 @@ *******************************************************************************/ package org.eclipse.lsp4e.command; -import static org.eclipse.lsp4e.command.LSPCommandHandler.*; +import static org.eclipse.lsp4e.command.LSPCommandHandler.LSP_COMMAND_PARAMETER_ID; +import static org.eclipse.lsp4e.command.LSPCommandHandler.LSP_PATH_PARAMETER_ID; import java.net.URI; import java.util.ArrayList; @@ -33,6 +34,7 @@ import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4e.LanguageServerPlugin; import org.eclipse.lsp4e.command.internal.CommandEventParameter; +import org.eclipse.lsp4e.internal.JsonUtil; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; @@ -192,7 +194,7 @@ private static WorkspaceEdit createWorkspaceEdit(List commandArguments, } else if (arg instanceof TextEdit textEdit) { currentEntry.value.add(textEdit); } else if (arg instanceof Map) { - final var gson = new Gson(); // TODO? retrieve the GSon used by LS + Gson gson = JsonUtil.LSP4J_GSON; TextEdit edit = gson.fromJson(gson.toJson(arg), TextEdit.class); if (edit != null) { currentEntry.value.add(edit); @@ -210,7 +212,7 @@ private static WorkspaceEdit createWorkspaceEdit(List commandArguments, } } } else if (arg instanceof JsonArray jsonArray) { - final var gson = new Gson(); // TODO? retrieve the GSon used by LS + Gson gson = JsonUtil.LSP4J_GSON; jsonArray.forEach(elt -> { TextEdit edit = gson.fromJson(gson.toJson(elt), TextEdit.class); if (edit != null) { @@ -218,7 +220,7 @@ private static WorkspaceEdit createWorkspaceEdit(List commandArguments, } }); } else if (arg instanceof JsonObject jsonObject) { - final var gson = new Gson(); // TODO? retrieve the GSon used by LS + Gson gson = JsonUtil.LSP4J_GSON; WorkspaceEdit wEdit = gson.fromJson(jsonObject, WorkspaceEdit.class); if (wEdit != null) { Map> entries = wEdit.getChanges(); diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/JsonUtil.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/JsonUtil.java new file mode 100644 index 000000000..29a27c806 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/JsonUtil.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. + * 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: + * See git history + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import java.util.Map; +import java.util.Objects; + +import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler; + +import com.google.gson.Gson; + +/** + * Provides a {@link Gson} instance which can properly serialize and deserialize LSP4J JSON-RPC objects + */ +public class JsonUtil { + + public static final Gson LSP4J_GSON = Objects.requireNonNull(new MessageJsonHandler(Map.of()).getGson()); + +}