Skip to content

Support for 'xml/executeClientCommand' protocol extension #543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion org.eclipse.wildwebdeveloper.xml/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Require-Bundle: org.eclipse.tm4e.registry;bundle-version="0.3.0",
org.eclipse.ui;bundle-version="3.105.0",
org.eclipse.ui.ide;bundle-version="3.14.0",
org.eclipse.ui.genericeditor;bundle-version="1.0.0",
org.eclipse.core.net;bundle-version="1.3.0"
org.eclipse.core.net;bundle-version="1.3.0",
org.eclipse.lsp4j.jsonrpc
Bundle-ActivationPolicy: lazy
Bundle-Activator: org.eclipse.wildwebdeveloper.xml.internal.Activator
Export-Package: org.eclipse.wildwebdeveloper.xml;x-friends:="org.eclipse.m2e.editor.lemminx"
1 change: 1 addition & 0 deletions org.eclipse.wildwebdeveloper.xml/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
point="org.eclipse.lsp4e.languageServer">
<server
class="org.eclipse.wildwebdeveloper.xml.internal.XMLLanguageServer"
clientImpl="org.eclipse.wildwebdeveloper.xml.internal.XmlLanguageClientImpl"
id="org.eclipse.wildwebdeveloper.xml"
label="XML Language Server"
singleton="true">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package org.eclipse.wildwebdeveloper.xml.internal;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add EPLv2 copyright header.


import static org.eclipse.lsp4e.command.LSPCommandHandler.LSP_COMMAND_PARAMETER_ID;
import static org.eclipse.lsp4e.command.LSPCommandHandler.LSP_PATH_PARAMETER_ID;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;

import org.eclipse.core.commands.Category;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IParameter;
import org.eclipse.core.commands.IParameterValues;
import org.eclipse.core.commands.ITypedParameter;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.ParameterType;
import org.eclipse.core.commands.ParameterValuesException;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.handlers.IHandlerService;

@SuppressWarnings("restriction")
class CommandExecutor {

private static final String LSP_COMMAND_CATEGORY_ID = "org.eclipse.lsp4e.commandCategory"; //$NON-NLS-1$
private static final String LSP_COMMAND_PARAMETER_TYPE_ID = "org.eclipse.lsp4e.commandParameterType"; //$NON-NLS-1$
private static final String LSP_PATH_PARAMETER_TYPE_ID = "org.eclipse.lsp4e.pathParameterType"; //$NON-NLS-1$

public CompletableFuture<Object> executeClientCommand(String id, Object... params) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like all that should be placed in LSP4E, and even that LSP4E does already have such code available. We should instead investigate a better way to expose the LSP4E command handling as API and consume it here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alex is a bit busy so I will continue to push this forwards. I just created a gerrit patch merging in this code with the lsp4e 'CommandExecutor'.

See: https://git.eclipse.org/r/c/lsp4e/lsp4e/+/170859

I suggest we start by discussion / resolving that patch via gerrit. And once that is done I can then amend this PR here to remove the obsolete wild web copy of CommandExecutor and use the one from lsp4e instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: you will see that my patch exports internal package. This is probably not what we want in the end. But I wanted to avoid moving the CommandExecutor class to another package as that would make it a lot harder to see what was changed in this class. So I think its better we review the change details first, and once we are agreed they are fine, we can then discuss / decide how to expose the api publically.

Copy link
Contributor

@kdvolder kdvolder Nov 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay so I just updated the PR. The CommandExecutor has been removed and now using the lsp4e CommandExecutor instead.

List<LanguageServer> commandHandlers = LanguageServiceAccessor.getActiveLanguageServers(handlesCommand(id));
if (commandHandlers != null && !commandHandlers.isEmpty()) {
if (commandHandlers.size() == 1) {
LanguageServer handler = commandHandlers.get(0);
return handler
.getWorkspaceService()
.executeCommand(new ExecuteCommandParams(id, Arrays.asList(params)));
} else if (commandHandlers.size() > 1) {
throw new IllegalStateException("Multiple language servers have registered to handle command '"+id+"'");
}
} else {
return executeCommandClientSide(new Command("Java LS command", id, Arrays.asList(params)));
}
return null;
}

private Predicate<ServerCapabilities> handlesCommand(String id) {
return (serverCaps) -> {
ExecuteCommandOptions executeCommandProvider = serverCaps.getExecuteCommandProvider();
if (executeCommandProvider != null) {
return executeCommandProvider.getCommands().contains(id);
}
return false;
};
}

@SuppressWarnings("unused") // ECJ compiler for some reason thinks handlerService == null is always false
private static CompletableFuture<Object> executeCommandClientSide(Command command) {
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench == null) {
return null;
}
IPath context = ResourcesPlugin.getWorkspace().getRoot().getLocation();
ParameterizedCommand parameterizedCommand = createEclipseCoreCommand(command, context, workbench);
if (parameterizedCommand == null) {
return null;
}
IHandlerService handlerService = workbench.getService(IHandlerService.class);
if (handlerService == null) {
return null;
}
return CompletableFuture.supplyAsync(() -> {
try {
return handlerService.executeCommand(parameterizedCommand, null);
} catch (ExecutionException | NotDefinedException e) {
Activator.getDefault().getLog().log(
new Status(IStatus.ERROR, Activator.getDefault().getBundle().getSymbolicName(), e.getMessage(), e));
return null;
} catch (NotEnabledException | NotHandledException e2) {
return null;
}
});
}

private static ParameterizedCommand createEclipseCoreCommand(Command command, IPath context,
IWorkbench workbench) {
// Usually commands are defined via extension point, but we synthesize one on
// the fly for the command ID, since we do not want downstream users
// having to define them.
String commandId = command.getCommand();
ICommandService commandService = workbench.getService(ICommandService.class);
org.eclipse.core.commands.Command coreCommand = commandService.getCommand(commandId);
if (!coreCommand.isDefined()) {
ParameterType commandParamType = commandService.getParameterType(LSP_COMMAND_PARAMETER_TYPE_ID);
ParameterType pathParamType = commandService.getParameterType(LSP_PATH_PARAMETER_TYPE_ID);
Category category = commandService.getCategory(LSP_COMMAND_CATEGORY_ID);
IParameter[] parameters = {
new CommandEventParameter(commandParamType, command.getTitle(), LSP_COMMAND_PARAMETER_ID),
new CommandEventParameter(pathParamType, command.getTitle(), LSP_PATH_PARAMETER_ID)};
coreCommand.define(commandId, null, category, parameters);
}

Map<Object, Object> parameters = new HashMap<>();
parameters.put(LSP_COMMAND_PARAMETER_ID, command);
parameters.put(LSP_PATH_PARAMETER_ID, context);
ParameterizedCommand parameterizedCommand = ParameterizedCommand.generateCommand(coreCommand, parameters);
return parameterizedCommand;
}


static class CommandEventParameter implements IParameter, ITypedParameter {

private final ParameterType paramType;
private final String name;
private String id;

public CommandEventParameter(ParameterType paramType, String name, String id) {
super();
this.paramType = paramType;
this.name = name;
this.id = id;
}

@Override
public ParameterType getParameterType() {
return paramType;
}

@Override
public String getId() {
return id;
}

@Override
public String getName() {
return name;
}

@Override
public IParameterValues getValues() throws ParameterValuesException {
return () -> Collections.emptyMap();
}

@Override
public boolean isOptional() {
return false;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.eclipse.wildwebdeveloper.xml.internal;

import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;

public interface XMLLanguageClient {

@JsonRequest("xml/executeClientCommand")
CompletableFuture<Object> executeClientCommand(ExecuteCommandParams params);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.eclipse.wildwebdeveloper.xml.internal;

import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4e.LanguageClientImpl;
import org.eclipse.lsp4j.ExecuteCommandParams;

@SuppressWarnings("restriction")
public class XmlLanguageClientImpl extends LanguageClientImpl implements XMLLanguageClient{

private CommandExecutor commandExecutor = new CommandExecutor();

public CompletableFuture<Object> executeClientCommand(ExecuteCommandParams params) {
String id = params.getCommand();
List<Object> args = params.getArguments();
return commandExecutor.executeClientCommand(id, args.toArray());
}


}