Dont Use LanguageServiceAccessor! #558
Replies: 17 comments 12 replies
-
For me, the use case is that we add some custom protocol to our language server and so we also need a means to get a reference to something that lets us 'talk to' the language server using that custom protocol. I figured that I need to get a hold of a 'LanguageServerWrapper' which seems to be the thing that represents a language server instance and has all the machinery to talk to the remote server. However, perhaps this PR: #543 might provide a different way to do what I want, as you (@angelozerr) suggested in our other discussion. I am not sure yet whether the PR does what I want/need because I don't understand how it works yet. What I will do is try to actually use it and see whether I can make it work for me. That will hopefully also allow me to give some constructive feedback on the PR. |
Beta Was this translation helpful? Give feedback.
-
I think the use case is similar, but I am not sure its the same. I am also not sure I understand it from your example. But I think it boils down to the fact that there are two places you can add custom api.
We are adding a request respone pattern so that we can ask the server to send us information. This means adding a thing like 'getListOfApp' that the server responds to. If you actually want to be able to make use of this new protocol you added you will need to obtain a reference to the 'LanguageServer' instance on the client side. I cannot send you a link to our code because sadly this project is not open-source. I hope it may become so as some point but at broadcom the policy is not to make new projects open-source by default. So let me try and explain it as best as possible what our 'use pattern' is. Say you have a server which has a model that contains information you want to display in a view (like a tree/structure viewer) . This information is very 'custom' so does not fit into any language-server protocol. For a simple example, (close to our use-case). Say this language server is watching the state of some applications deployed to a cluster. The view displays a list of applications. When the view opens up, it needs to get access to an object that it can use to send messages to the language server (and receive responses back). i.e. we want to have some code like (not actually correct code, but to convey the gist of it):
There are also more complex 'subscribe/even-stream' patterns which extend the protocol on the client (i.e. to receive notifcation/events for something you subsribed to). I think in your example you are extending the client-side api (messages a server can send to a client), and you can have code that reacts to those messages and does things on the client side. In our example there is that kind of thing as well, but there is also extra protocol on the other side (custom messages a client can send to a server). |
Beta Was this translation helpful? Give feedback.
-
Ok I think I understand more what you need. I have the impression that you need the same thing that we have to support workspace symbol (since you don't have file in parameters) https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/features/workspaceSymbol/LSPWorkspaceSymbolSupport.java by replacing:
and after that you consume the support like this: Our support are very advanced because they support cancel support which is very important to have the best performance. right? If it that, I think we should provide a base class which usesinternally LanguageServiceAccessor and implement a method like I mean you will just need to implement filter to collect your ls and cast the LanguageServer instance with your Custom Language Server API to consume your custom server service and collect data. My main goal is to avoid using LanguageServerWrapper and LanguageServiceAccessor and just using LSP4J LanguageServer and the support will give you the cancel support feature. Makes sense? |
Beta Was this translation helpful? Give feedback.
-
I will be happy to discuss with you, but let's merge the PR and see if it is enough for you otherwise let's do a meeting if my doc and PR doesn't cover your need :) |
Beta Was this translation helpful? Give feedback.
-
@kdvolder after some discussion with @fbricon we will provide a simple API to write
|
Beta Was this translation helpful? Give feedback.
-
@kdvolder I did the change, please read the DeveloperGuide.md from the PR, you should have the capability to write: CompletableFuture<List<Application>> applications =
LanguageServerManager.getInstance(project)
.getLanguageServer("Tanzu")
.thenApply(languageServerItem ->
languageServerItem != null ? languageServerItem.getServer() // here getServer is used because we are sure that server is initialized
: null)
.thenCompose(ls -> {
if (ls == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
TanzuServerAPI myServer = (TanzuServerAPI) ls;
return myServer.getApplications();}
); or Project project = ...
CompletableFuture<@Nullable LanguageServerItem> languageServerFuture =
LanguageServerManager.getInstance(project)
.getLanguageServer("Tanzu"); In an another piece of code: CompletableFuture<List<Application>> applications =
languageServerFuture
.thenCompose(languageServerItem -> {
if (languageServerItem != null) {
return CompletableFuture.completedFuture(null);
}
return languageServerItem.getInitializedServer(); // here getInitializedServer is used because language server could be stopped and must be restarted
})
.thenCompose(ls -> {
if (ls == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
TanzuServerAPI myServer = (TanzuServerAPI) ls;
return myServer.getApplications();}
); |
Beta Was this translation helpful? Give feedback.
-
Great!
Yes it is that. When LanguageServerItem is created, the ls is initialized and the server field (used in getServer) is set with it. If you stop the language server, the server field has a reference of a language server which is shutdown. We use LanguageServerItem internally and we use getServer. But as LanguageServerItem could be cached, I have add getInitializeServer which starts the ls if needed and returns the proper language server. Hope you will understand my explanation. The doc is not perfect,any PR are welcome!
Ok
Don't use this hack, you can configure that now, see https://github.com/redhat-developer/lsp4ij/blob/main/docs/LSPApi.md#lsp-client-features by overriding
If I understand, you should have a queue with events which belong to a project and this queue must not loose some events when server is stopped. If it that, I will create a class like
If your need is to just refresh a View, I think its not interesting to use AbstractLSPFeatureSupport
Int he case of main LSP features, the cache must be evicted when PsiFile is updated. It is a generic behaviour. For some LSP features like completion, the future is canceled when offset changes in the load. Anyway if your need is just to collect some data like list of Application for a View you don't need this support class. If you need more API please tell me. |
Beta Was this translation helpful? Give feedback.
-
I don't. That is assuming I have a way to keep the server alive while I am using it. I would only need to be able to track server status as a workaround to cope with the 'unwanted shutdown' and do some hacky stuff around that to
With a proper protocol that provides assurances that server doesn't get terminated while it is in use we don't/wouldn't need to implement these workarounds. We now have the 'keepAlive' which serves the need just fine. However, as I said, I think it has this minor bug that there won't be a 'trigger' to shutdown the server when both of these are true at the same time:
In this scenario language server will stay alive even thoug keepAlive would return false (if it were called again). This is a bit of en edge case and maybe not very important. I am not sure how often this would happen and how much it would matter (and even with that bug... this is still better than setting a really long timeout). |
Beta Was this translation helpful? Give feedback.
-
Formally, I guess it would. But its really not what I want. I really don't need to track the server status what I really need instead is a way to tell the languageservermanager "please don't terminate this server while I am using it". Tracking server status and reacting to it would only be a 'coping mechanism' to handle something we don't actually want to happen in the first place. |
Beta Was this translation helpful? Give feedback.
-
Its is a specific usecase, right? You can stop/start the language server with Api https://github.com/redhat-developer/lsp4ij/blob/main/docs/DeveloperGuide.md#start--stop |
Beta Was this translation helpful? Give feedback.
-
As I am trying to implement our 'keepAlive' with some real logic rather than just have it always returning true... I am realizing that I need more context information to do so. Particularly, I have no way to know which server a 'createClientFeatures' call relates to. So since subscriptions belong to specific servers we need some way to know that. Perhaps passing in the Project would be enough, or a reference to the |
Beta Was this translation helpful? Give feedback.
-
I suppose so, but then I need to be able to avoid stopping it when there are open editors. Really I do not want to actually stop it. I just want a way to be able to say "Do not stop it while I am using it". That is not quite the same. |
Beta Was this translation helpful? Give feedback.
-
I don't understand what you suggest there but even so I think its not what I want. I don't want to shutdown the language server. I want the lsp4ij to manage ls lifecycle pretty much the way it already does. The only thing I want to do is put an additional 'keep alive' claim on the server process for some durarion. I.e. just like an open editor right now represents a 'keep alive' claim. So would I want our view to be a 'keep alive claim' as well. I don't really want to say 'please shut down the server now'. I want to leave that up to lsp4ij. All I want to be able to do is something like this:
At the point where serverAccessClaim is disposed, the number of open claims (tracked by lsp4ij) will be decreased by 1 and if it hits 0 and at the same time there are no open editors (which also represent a kind of 'accessClaim') then shutdown sequence is triggered. I don't actually want to call server.shutdown instead of 'dispose'. The reason being that I think only lsp4ij itself is in the right position to make the decision whether 'there is anything else still using the server'. The call to 'dispose' there says 'I no longer care if you keep the server alive'. It doesn't say 'shut down the server now'. Lps4ij now knows that I don't need the server anymore and can re-evaluate whether it can/should shutdown the server or not using information from 'all possible other things that might need the server still alive'. |
Beta Was this translation helpful? Give feedback.
-
I suppose, its a matter of poinf of view. But I think its more of a generalization of what you are already doing with respect to open editors. So I think these kinds of 'leases' on a languageserver would be a pretty generic thing to add to the api. I also think its very natural when you are 'using' a language server you'd want to have assurances that it doesn't get shutdown while you are using it. So I think it represents a pretty general kind of need for anyone who'd extend the language server with some custom protocol and then wants keep hold of a reference to the server to talk to it. |
Beta Was this translation helpful? Give feedback.
-
It makes sense for the most part. Also interesting, I hadn't actually thought about the fact that user can stop ls by menu. I guess that's kind of 'annoying' but its not as fundamentally problematic. If the user really does that, then I suppose we'd need our view to be able to just detect that this happened and rather than override this user action and reconnect, we'd need to display some kind of a message in the view indicating it has stopped working because basically the user terminated the connection themselves. Or we add that api you suggested to 'disable/disallow' that user action. That's probably actually better/nicer.
This is the part that still doesn't quite make a lot of sense to me. To be clear. I don't really have a problem that there is an api to allow someone to do that really. I just don't want to use that api. It is not the api for me. I don't want to explicitly call 'stop the server' api because its too difficult for me to implement that correctly and without duplicating the logic you already have implemented to detect 'no more open editors'. I suppose I could, assuming you provide api for me to check whether there are open editors. So I can then effectively duplicate the termination logic and then 'add my own twist' on top op it (i.e. I will need to add a check right before I call 'stop the server' to make sure there are no more open editors. But I really don't think that is a good solution. What happens if this logic changes in lsp4ij? Also there's still the caveat that I can't really correctly implement the 'keepServerAlive' with the api as it designed now. As I tried to explain somewhere in the wall of text above (too much text, its easy to miss). I do not actually know at the point where `keepServerAlive' is called which server needs to be kept alive. This is not that big deal, but I think the method needs some extra argument(s) to provide enough context to the implementor to know which server we are being asking to 'keepAlive'. |
Beta Was this translation helpful? Give feedback.
-
As it seems kind of hard for me to explain it any better and we don't seem to be getting on the same page because I am probably not explaining myself clearly enough. I thought the best thing to do would be to quickly hash out an implementation of my idea: Note: this PR has not been tested and could have logic bugs in it. Think of it more as a way to actually show what I'm thinking than a working implementation. |
Beta Was this translation helpful? Give feedback.
-
Hi I am not using |
Beta Was this translation helpful? Give feedback.
-
I have tried to add a comment in
LanguageServiceAccessor
but I create an announce about that.Please don't use LanguageServiceAccessor! This class comes from LSP4E and the API is not stable. For instance LanguageServiceAccessor uses Predicate to check if the language server has some capability, but ServerCapability is for static server capability, not for dynamic server capability.
By using this Predicate we cannot support documentSelector for instance you should never done this kind of code https://github.com/themartdev/intellij-gleam/blob/9eea2c41723d6c2b99bf15e70b04545952b4f194/src/main/kotlin/com/github/themartdev/intellijgleam/ide/formatter/lsp/GleamAbstractLspFormattingServiceProxy.kt#L38
//cc @FalsePattern @themartdev
I understand you are doing that because current LSP4IJ version doesn't allow LSP formatting when it already exists an formatter, but LSP API PR which will be available in 0.7.0 #543 will fix this problem (see LSPApi.md doc from this PR).
Today I have never seen some usecase to use LanguageServiceAccessor in an external plugin. If you have a usecase, please share with us and in this case we should provide a clean API (with an another class I think).
cc @CppCXY @InSyncWithFoo @seachicken @KUGDev @AlexWeinstein92 @aparnamichael @MituuZ @turkeylurkey @mrglavas @koxudaxi @FalsePattern @onriv @kristofvb @themartdev
Beta Was this translation helpful? Give feedback.
All reactions