The purpose of notifications is to provide updates without being asked. In our case, it makes sense when we have updated the repository (after parsing one or more files) to send such a notification. Otherwise, the front end needs to "guess" when the information may have changed and request it again.
So the task here is to try and turn the process around, and instead of sending a request for the information we requested, for the back end to automatically send it when it is ready.
Reading through the specification, it seems that this is possible, but that it requires us to "extend" the protocol. But I'm not entirely sure how to do that. It turns out that while it's possible, it doesn't seem to me that it's "genuinely" supported at all.
On the server side, we need to define a new interface which extends LanguageClient and specify the new notification methods that we want, tagged with the @JsonNotification attribute, which specifies the command string that will be used in sending the message.
package ignorance;It should then just be a case of telling the LSPLauncher that this is the protocol we want to use, but none of the factory methods takes an interface - they all assume that you want to use LanguageClient. But fortunately, there is nothing magic about any of these methods, so we can extract it locally and make the change we need.
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.services.LanguageClient;
public interface ExtendedLanguageClient extends LanguageClient {
@JsonNotification("ignorance/tokens")
void sendTokens(Object object);
}
Launcher<ExtendedLanguageClient> launcher = createServerLauncher(server, in, out);
private static Launcher<ExtendedLanguageClient> createServerLauncher(IgnorantLanguageServer server, InputStream in, OutputStream out) {Likewise, on the client side, it should be just as simple as calling the onNotification method of the client, but this can only be done once the client is ready, and thus after communication has already started up - meaning that some messages might be sent (and lost) before the handler is installed. I tried a number of ways of working around this (there appears to be a notion of Features which can install handlers) but nothing fixed the fundamental problem, so I ended up having to add my own synchronization around this. But for now, here's the code that sets up the handler in the onReady method.
return new LSPLauncher.Builder<ExtendedLanguageClient>()
.setLocalService(server)
.setRemoteInterface(ExtendedLanguageClient.class)
.setInput(in)
.setOutput(out)
.create();
}
client.onReady().then(() => {So to handle the synchronization, we add another command with the message ignorance/readyForTokens. In the onReady method we call this AFTER configuring the notification handler.
client.onNotification("ignorance/tokens", () => {
console.log("received token notification");
});
client.onNotification("ignorance/tokens", () => {On the server side, we can then start sending messages when appropriate.
console.log("received token notification");
});
client.sendRequest(ExecuteCommandRequest.type, {
command: 'ignorance/readyForTokens',
arguments: [ ]
});
const tokensProvider = new TokensProvider();
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {Which just leaves the process of actually sending a message, which we want to do once we have finished parsing. There is a certain amount of off-camera wiring to make this happen reliably.
switch (params.getCommand()) {
case "java.lsp.requestTokens": {
System.err.println("requestTokens command called for " + params.getArguments().get(0));
return repo.allTokensFor(URI.create(((JsonPrimitive) params.getArguments().get(0)).getAsString()));
}
case "ignorance/readyForTokens": {
System.err.println("readyForTokens");
amReady = true;
return CompletableFuture.completedFuture(null);
}
default: {
System.err.println("cannot handle command " + params.getCommand());
return CompletableFuture.completedFuture(null);
}
}
}
private void parseAllFiles() {All the complex wiring is once again left as an exercise for the reader (see VSCODE_NOTIFICATIONS_COMPLEX_WIRING).
File file = new File(workspaceRoot.getPath());
parseDir(file);
client.sendTokens("hello, world");
}
No comments:
Post a Comment