eclipse.jdt.ls
eclipse.jdt.ls copied to clipboard
No delegateCommandHandler for java.apply.workspaceEdit
I'm using jdt.ls via LanguageClient-neovim, and I'm trying to use code actions. This is my file:
public class App {
public static void main(String[] args) {
JFrame frame = new JFrame();
}
}
JFrame is linted because I did not import it, and the LS suggests some code actions(this is from the log of the client - I just formatted the JSON lines to make them readable):
17:06:19 INFO [MainThread] Begin textDocument/codeAction
17:06:19 DEBUG [MainThread] =>
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/codeAction",
"params": {
"textDocument": {
"uri": "file:///files/tests/java/45/src/main/java/App.java"
},
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 14
}
},
"context": {
"diagnostics": [
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 14
}
},
"severity": 1,
"code": "16777218",
"source": "Java",
"message": "JFrame cannot be resolved to a type"
}
]
}
}
}
17:06:19 DEBUG [RPC-java ] <= {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Oct 3, 2017 5:05:52 PM \u003e\u003e document/codeAction"}}
17:06:19 DEBUG [RPC-java ] <=
{
"jsonrpc": "2.0",
"id": "1",
"result": [
{
"title": "Change to 'JobName' (javax.print.attribute.standard)",
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "import javax.print.attribute.standard.JobName;"
},
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "\n\n"
},
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 8
}
},
"newText": "JobName"
},
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 14
}
},
"newText": ""
}
]
}
}
]
},
{
"title": "Import 'JFrame' (javax.swing)",
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "import javax.swing.JFrame;"
},
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "\n\n"
}
]
}
}
]
},
{
"title": "Add type parameter 'JFrame' to 'main(String[])'",
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 18
}
},
"newText": "<"
},
{
"range": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 18
}
},
"newText": "JFrame"
},
{
"range": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 18
}
},
"newText": "> "
}
]
}
}
]
}
]
}
17:06:19 DEBUG [MainThread] state.codeActionCommands: [] =>
[
{
"title": "Change to 'JobName' (javax.print.attribute.standard)",
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "import javax.print.attribute.standard.JobName;"
},
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "\n\n"
},
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 8
}
},
"newText": "JobName"
},
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 14
}
},
"newText": ""
}
]
}
}
]
},
{
"title": "Import 'JFrame' (javax.swing)",
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "import javax.swing.JFrame;"
},
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "\n\n"
}
]
}
}
]
},
{
"title": "Add type parameter 'JFrame' to 'main(String[])'",
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 18
}
},
"newText": "<"
},
{
"range": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 18
}
},
"newText": "JFrame"
},
{
"range": {
"start": {
"line": 1,
"character": 18
},
"end": {
"line": 1,
"character": 18
}
},
"newText": "> "
}
]
}
}
]
}
]
17:06:19 INFO [MainThread] End textDocument/codeAction
So I pick Import 'JFrame' (javax.swing):
17:06:44 INFO [MainThread] Begin workspace/executeCommand
17:06:44 DEBUG [MainThread] =>
{
"jsonrpc": "2.0",
"id": 2,
"method": "workspace/executeCommand",
"params": {
"command": "java.apply.workspaceEdit",
"arguments": [
{
"changes": {
"file:///files/tests/java/45/src/main/java/App.java": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "import javax.print.attribute.standard.JobName;"
},
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "\n\n"
},
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 8
}
},
"newText": "JobName"
},
{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 14
}
},
"newText": ""
}
]
}
}
]
}
}
17:06:44 DEBUG [RPC-java ] <= {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Oct 3, 2017 5:05:52 PM \u003e\u003e workspace/executeCommand"}}
17:06:44 DEBUG [RPC-java ] <= {"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"No delegateCommandHandler for java.apply.workspaceEdit"}}
17:06:44 ERROR [MainThread] {"jsonrpc": "2.0", "id": "2", "error": {"code": -32601, "message": "No delegateCommandHandler for java.apply.workspaceEdit"}}
17:06:44 INFO [MainThread] End workspace/executeCommand
And get the error message - No delegateCommandHandler for java.apply.workspaceEdit
The LSP specs does not seem to refer to delegateCommandHandlers, and from #346 it looks like it's jdt.ls' extension - but I don't see any instructions on how to use it.
So... what do I need to do to add support for it in a client?
Yeah documenting jdt.ls specific commands is in our backlog (#314), sorry about that.
java.apply.workspaceEdit is not related to delegateCommandHandlers actually (those are server side extensions). It's a command that needs to be implemented by clients. See how it's done in vscode-java:
https://github.com/redhat-developer/vscode-java/blob/9b0f0aca80cbefabad4c034fb5dd365d029f6170/src/extension.ts#L155-L160
Basically, the server sends workspace edits to the client, and the client is responsible for applying them.
Isn't that how code actions work? Why do you need a special command for that?
I think the issue is that protocol doesn't specify clearly how client is going to consume a Command(https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#command) after user select one, which could have at least two cases here,
- client sends the selected
Commandto server, server responds back withworkspaceEdit, then client applies those edits - client itself figures out how to apply edits based on selected
Command
sourcegraph/javascript-typescript-langserver chosen option 1 and LanguageClient-neovim followed the same pattern. And it looks here eclipse.jdt did option 2.
So much for having a unified protocol that solves the matrix problem...
The main difference between 1 and 2 is when the TextEdits are the calculated otherwise both require a custom command to be implemented by the client. 2nd way works well for jdt.ls at the moment because the way we determine code actions gets us very close to calculating TextEdits anyway hence more efficient.
But option 2 doesn't conform with the protocol. https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
When the command is selected the server should be contacted again (via the workspace/executeCommand) request to execute the command.
If the need for option 2 is real shouldn't it be pushed to the protocol?
There is a proposal to support returning an edit instead of a command: https://github.com/Microsoft/language-server-protocol/issues/178
But option 2 doesn't conform with the protocol. https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
When the command is selected the server should be contacted again (via the workspace/executeCommand) request to execute the command.
If the need for option 2 is real shouldn't it be pushed to the protocol?
This provision was added AFTER jdt.ls implemented code actions this way. So "option 2" was at one time in the protocol, but was removed. We should probably move to using WorkspaceEdits, now that the can contain resource changes, but that would break existing clients.
Hi guys - any update on this one? I see that lots of other clients are busy implementing custom commands (i.e. https://github.com/autozimu/LanguageClient-neovim/issues/158) which as mentioned above is unnecessary.
Won't most clients be able to handle WorkspaceEdits now? If so then moving across should be fine?
Cheers
Won't most clients be able to handle WorkspaceEdits now?
What makes you think that?
Won't most clients be able to handle WorkspaceEdits now?
What makes you think that?
I saw the linked ticket had been closed back in August. But if most have not then it would have to wait, yes. (https://github.com/prabirshrestha/vim-lsp this one for instance does seem to)
Was testing JDT-LS integration with LSP4E and ran into this issue as well.
Would it be possible to replace usage of "java.apply.workspaceEdit" in codeAction with workspaceEdit?
It seems to be mentioned in CodeActionHandler.java:
if (preferenceManager.getClientPreferences().isSupportedCodeActionKind(proposal.getKind())) {
// TODO: Should set WorkspaceEdit directly instead of Command
CodeAction codeAction = new CodeAction(name);
codeAction.setKind(proposal.getKind());
codeAction.setCommand(command);
codeAction.setDiagnostics(context.getDiagnostics());
return Optional.of(Either.forRight(codeAction));
} else {
return Optional.of(Either.forLeft(command));
}
Also, could we retitle this issue to something like Replace usage of java.apply.workspaceEdit in codeAction by workspaceEdit?
The fix was reverted because of a regression. See https://github.com/eclipse/eclipse.jdt.ls/pull/1256#issuecomment-553582214
Reverted the fix once again, sorry. Because of a formatting regression on vscode. See https://github.com/eclipse/eclipse.jdt.ls/pull/1059#issuecomment-559403554
For the record, this can be fixed, while staying... ugh... bug compatible with vscode*.
JDTDelegateCommandHandler is a catch-all command handler. It's pretty trivial to extend it to support java.apply.workspaceEdit
diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java
index ec43feba..d0794361 100644
--- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java
+++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java
@@ -49,6 +49,14 @@ public class JDTDelegateCommandHandler implements IDelegateCommandHandler {
// workspaceEdit on the custom command.
return result;
}
+ case "java.command.applyEdit":^M
+ final WorkspaceEdit r = (WorkspaceEdit) arguments.get(0);^M
+ if (JavaLanguageServerPlugin.getPreferencesManager().getClientPreferences().isWorkspaceApplyEditSupported()) {^M
+ JavaLanguageServerPlugin.getInstance().getClientConnection().applyWorkspaceEdit((WorkspaceEdit) r);^M
+ return new Object();^M
+ } else {^M
+ return r;^M
+ }^M
case "java.project.resolveSourceAttachment":
return SourceAttachmentCommand.resolveSourceAttachment(arguments, monitor);
case "java.project.updateSourceAttachment":
Note that there's also org.eclipse.jdt.ls.core/plugin.xml, which I don't know the purpose of. Anyway...
diff --git a/org.eclipse.jdt.ls.core/plugin.xml b/org.eclipse.jdt.ls.core/plugin.xml
index 75036602..209f41f9 100644
--- a/org.eclipse.jdt.ls.core/plugin.xml
+++ b/org.eclipse.jdt.ls.core/plugin.xml
@@ -54,6 +54,9 @@
<command
id="java.edit.organizeImports">
</command>
+ <command
+ id="java.apply.workspaceEdit">
+ </command>
<command
id="java.project.updateSourceAttachment">
</command>
Doing this, the response to textDocument/codeAction remains the same and the hacks in vscode still "work" (quotation marks are there because hacks don't work, ever), but clients who respect the protocol can still make a workspace/executeCommand request.
I may have missed something (after compiling jdt, I have simply copied org.eclipse.jdt.ls.product/target/repository/ to where my client expects it to be), but the WorkspaceExecuteCommandHandler still doesn't know that JDTDelegateCommandHandler can handle java.apply.workspaceEdit.
I don't think I'll be working any further on this bug, but I would like to point out that not supporting executeCommand in this case is a protocol violation, hence why so many clients have ran into this bug.
* That basically was the reason why #1278 was reverted.
Any update on this? Seems to have been open for a long time - I guess it's not ever going to get done?
Highly required thing, wish to see it fixed. Thanks in advance for all developers!
With the new insertTextModeSupport client capability, jdtls should be able to determine what whitespace strategy to use, no?
Are there any plans to fix this behavior?
@fbricon given that you looked at previous attempts at fixing this could you take a look at linked PR, it should leave this compatible with vscode while allowing compliant clients to work as well. It basically does what @bstaletic suggested above 3 years ago