Skip to content

Commit

Permalink
fix: instantiate JsonObject with the classloader of the IJ plugin which
Browse files Browse the repository at this point in the history
defines the action

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Dec 6, 2023
1 parent bfc2aa2 commit eaee978
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 67 deletions.
168 changes: 108 additions & 60 deletions src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
******************************************************************************/
package com.redhat.devtools.lsp4ij.commands;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.*;
import com.intellij.ide.DataManager;
import com.intellij.ide.plugins.cl.PluginClassLoader;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
Expand All @@ -36,7 +35,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -63,15 +64,12 @@ public class CommandExecutor {
* the server, nor the client are able to handle the command explicitly, a
* heuristic method will try to interpret the command locally.
*
* @param command
* the LSP Command to be executed. If {@code null} this method will
* do nothing.
* @param documentUri
* the URI of the document for which the command was created
* @param languageServerId
* the ID of the language server for which the {@code command} is
* applicable. If {@code null}, the command will not be executed on
* the language server.
* @param command the LSP Command to be executed. If {@code null} this method will
* do nothing.
* @param documentUri the URI of the document for which the command was created
* @param languageServerId the ID of the language server for which the {@code command} is
* applicable. If {@code null}, the command will not be executed on
* the language server.
*/
public static void executeCommand(Project project, Command command, URI documentUri,
String languageServerId) {
Expand All @@ -81,7 +79,7 @@ public static void executeCommand(Project project, Command command, URI document
if (executeCommandServerSide(project, command, documentUri, languageServerId)) {
return;
}
if (executeCommandClientSide(project, command, documentUri)) {
if (executeCommandClientSide(documentUri, command, null, project)) {
return;
}
// tentative fallback
Expand Down Expand Up @@ -142,7 +140,7 @@ private static CompletableFuture<LanguageServer> getLanguageServerForCommand(Pro
});
}

private static boolean executeCommandClientSide(Project project, Command command, URI documentUri) {
public static boolean executeCommandClientSide(URI documentUri, Command command, Component source, Project project) {
Application workbench = ApplicationManager.getApplication();
if (workbench == null) {
return false;
Expand All @@ -151,7 +149,7 @@ private static boolean executeCommandClientSide(Project project, Command command
if (parameterizedCommand == null) {
return false;
}
DataContext dataContext = createDataContext(project, command, documentUri);
DataContext dataContext = createDataContext(documentUri, command, parameterizedCommand, source, project);
ActionUtil.invokeAction(parameterizedCommand, dataContext, ActionPlaces.UNKNOWN, null, null);
return true;
}
Expand All @@ -164,15 +162,64 @@ private static AnAction createIDEACoreCommand(Command command) {
return ActionManager.getInstance().getAction(commandId);
}

private static DataContext createDataContext(Project project, Command command, URI documentUri) {

private static DataContext createDataContext(URI documentUri, Command command, AnAction action, Component source, Project project) {
SimpleDataContext.Builder contextBuilder = SimpleDataContext.builder();
contextBuilder.add(CommonDataKeys.PROJECT, project)
.add(LSP_COMMAND, command)
.add(LSP_COMMAND_DOCUMENT_URI, documentUri);
if (source != null) {
contextBuilder.setParent(DataManager.getInstance().getDataContext(source));
}
updateCommandArguments(command, action.getClass().getClassLoader());
contextBuilder
.add(CommonDataKeys.PROJECT, project)
.add(LSP_COMMAND, command);
if (documentUri != null) {
contextBuilder.add(LSP_COMMAND_DOCUMENT_URI, documentUri);
}
return contextBuilder.build();
}

private static void updateCommandArguments(Command command, ClassLoader classLoader) {
List<Object> arguments = command.getArguments();
if (arguments == null || arguments.isEmpty()) {
return;
}
for (int i = 0; i < arguments.size(); i++) {
Object arg = arguments.get(i);
if (arg instanceof JsonElement) {
JsonElement elt = (JsonElement) arg;
if (elt.getClass().getClassLoader() != classLoader) {
if (classLoader instanceof PluginClassLoader) {
try {
Object newElt = null;
Class<?> cl = ((PluginClassLoader) classLoader).tryLoadingClass(JsonParser.class.getName(), true);
if (cl != null) {
try {
// public static JsonElement parseString(String json) throws JsonSyntaxException {
Method parseString = cl.getDeclaredMethod("parseString", String.class);
newElt = parseString.invoke(cl, elt.toString());
} catch (Exception e) {
try {
// public JsonElement parse(String json) throws JsonSyntaxException {
Method parse = cl.getDeclaredMethod("parse", String.class);
newElt = parse.invoke(cl.getDeclaredConstructor().newInstance(), elt.toString());
} catch (Exception e1) {

}
}
arguments.set(i, newElt);
}
if (newElt != null) {
arguments.set(i, newElt);
}
} catch (Exception e) {

}
}
}
}

}
}

// TODO consider using Entry/SimpleEntry instead
private static final class Pair<K, V> {
K key;
Expand All @@ -185,6 +232,7 @@ private static final class Pair<K, V> {
}

// this method may be turned public if needed elsewhere

/**
* Very empirical and unsafe heuristic to turn unknown command arguments into a
* workspace edit...
Expand All @@ -202,55 +250,55 @@ private static WorkspaceEdit createWorkspaceEdit(List<Object> commandArguments,
return Collections.singleton(item).stream();
}
}).forEach(arg -> {
if (arg instanceof String) {
if (arg instanceof String) {
changes.put(currentEntry.key.toString(), currentEntry.value);
VirtualFile resource = LSPIJUtils.findResourceFor((String) arg);
if (resource != null) {
currentEntry.key = LSPIJUtils.toUri(resource);
currentEntry.value = new ArrayList<>();
}
} else if (arg instanceof WorkspaceEdit) {
changes.putAll(((WorkspaceEdit) arg).getChanges());
} else if (arg instanceof TextEdit) {
currentEntry.value.add((TextEdit) arg);
} else if (arg instanceof Map) {
Gson gson = new Gson(); // TODO? retrieve the GSon used by LS
TextEdit edit = gson.fromJson(gson.toJson(arg), TextEdit.class);
if (edit != null) {
currentEntry.value.add(edit);
}
} else if (arg instanceof JsonPrimitive) {
JsonPrimitive json = (JsonPrimitive) arg;
if (json.isString()) {
changes.put(currentEntry.key.toString(), currentEntry.value);
VirtualFile resource = LSPIJUtils.findResourceFor((String) arg);
VirtualFile resource = LSPIJUtils.findResourceFor(json.getAsString());
if (resource != null) {
currentEntry.key = LSPIJUtils.toUri(resource);
currentEntry.value = new ArrayList<>();
}
} else if (arg instanceof WorkspaceEdit) {
changes.putAll(((WorkspaceEdit) arg).getChanges());
} else if (arg instanceof TextEdit) {
currentEntry.value.add((TextEdit) arg);
} else if (arg instanceof Map) {
Gson gson = new Gson(); // TODO? retrieve the GSon used by LS
TextEdit edit = gson.fromJson(gson.toJson(arg), TextEdit.class);
}
} else if (arg instanceof JsonArray) {
Gson gson = new Gson(); // TODO? retrieve the GSon used by LS
JsonArray array = (JsonArray) arg;
array.forEach(elt -> {
TextEdit edit = gson.fromJson(gson.toJson(elt), TextEdit.class);
if (edit != null) {
currentEntry.value.add(edit);
}
} else if (arg instanceof JsonPrimitive) {
JsonPrimitive json = (JsonPrimitive) arg;
if (json.isString()) {
changes.put(currentEntry.key.toString(), currentEntry.value);
VirtualFile resource = LSPIJUtils.findResourceFor(json.getAsString());
if (resource != null) {
currentEntry.key = LSPIJUtils.toUri(resource);
currentEntry.value = new ArrayList<>();
}
}
} else if (arg instanceof JsonArray) {
Gson gson = new Gson(); // TODO? retrieve the GSon used by LS
JsonArray array = (JsonArray) arg;
array.forEach(elt -> {
TextEdit edit = gson.fromJson(gson.toJson(elt), TextEdit.class);
if (edit != null) {
currentEntry.value.add(edit);
}
});
} else if (arg instanceof JsonObject) {
Gson gson = new Gson(); // TODO? retrieve the GSon used by LS
WorkspaceEdit wEdit = gson.fromJson((JsonObject) arg, WorkspaceEdit.class);
Map<String, List<TextEdit>> entries = wEdit.getChanges();
if (wEdit != null && !entries.isEmpty()) {
changes.putAll(entries);
} else {
TextEdit edit = gson.fromJson((JsonObject) arg, TextEdit.class);
if (edit != null && edit.getRange() != null) {
currentEntry.value.add(edit);
}
});
} else if (arg instanceof JsonObject) {
Gson gson = new Gson(); // TODO? retrieve the GSon used by LS
WorkspaceEdit wEdit = gson.fromJson((JsonObject) arg, WorkspaceEdit.class);
Map<String, List<TextEdit>> entries = wEdit.getChanges();
if (wEdit != null && !entries.isEmpty()) {
changes.putAll(entries);
} else {
TextEdit edit = gson.fromJson((JsonObject) arg, TextEdit.class);
if (edit != null && edit.getRange() != null) {
currentEntry.value.add(edit);
}
}
}
});
if (!currentEntry.value.isEmpty()) {
changes.put(currentEntry.key.toString(), currentEntry.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,7 @@ public boolean isLanguageSupported(@NotNull Language language) {

protected void executeClientCommand(@NotNull Component source, @NotNull Command command, @NotNull Project project) {
if (command != null) {
AnAction action = ActionManager.getInstance().getAction(command.getCommand());
if (action != null) {
DataContext context = SimpleDataContext.getSimpleContext(CommandExecutor.LSP_COMMAND, command, DataManager.getInstance().getDataContext(source));
action.actionPerformed(new AnActionEvent(null, context,
ActionPlaces.UNKNOWN, new Presentation(),
ActionManager.getInstance(), 0));
}
CommandExecutor.executeCommandClientSide(null, command, source, project);
}
}

Expand Down

0 comments on commit eaee978

Please sign in to comment.