-
Notifications
You must be signed in to change notification settings - Fork 54
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
New API: Add single-server document executor #434
New API: Add single-server document executor #434
Conversation
1e537ef
to
85eb2ee
Compare
@rubenporras as requested! I even added a test ;-) |
* Executor for a single server - for follow-up requests that should go to the server that returned an earlier response | ||
* | ||
*/ | ||
public static class SingleServerDocumentExecutor extends LanguageServerDocumentExecutor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that to be consistent with the names of the other classes, it should be named SingleLanguageServerDocumentExecutor
, in the name of the other classes we use LanguageServer
and not just Server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. The names are becoming a bit cumbersome but I guess that's what var
was invented for ;-)
return this.serverWrapper.execute(fn); | ||
} | ||
|
||
public boolean supports(final Predicate<ServerCapabilities> predicate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it not right that this executor does not respect the withFilter
or withPredicate
(even though one could call it) and instead offers a new method to do the filter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have amended it so that the inherited methods collectAll()
etc will respect it, but kept execute()
as a method for cases where the code already knows what the server is and what capabilities it has and just wants a 'get it done' request method.
See summary of revisions below
|
||
private final @NonNull LanguageServerWrapper serverWrapper; | ||
|
||
SingleServerDocumentExecutor(final @NonNull IDocument document, final @NonNull LanguageServerWrapper serverWrapper) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the LanguageServerDocumentExecutor uses the document to find out the wrappers and connect them if needed, but I do not think this executor uses the document for anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are (partly) right - one of the motivations for this class was as a replacement for DocumentInfo
, which effectively bundles a reference to a single LS with a document - it's virtually a POJO with no brains. In some of the code I was trying to port, the existing logic passed through a DocumentInfo
as a single field. My first attempt replaced this with passing around a LanguageServerWrapper
and an IDocument
as individual arguments. But then I thought of taking your LanguageServerWrapperExecutor
idea and marrying it with LanguageServerDocumentExecutor
.
I think this gives a nicer way of rewriting the existing code, and also means versioning support is available.
@ahmedneilhussain, I have prepared #435, it is only the API in LanguageServers extracted out of #396, it have added a getServer method as you propose and have also taken care of handling the filter if given. |
HI @rubenporras I think you raise some good points. I proposed this class for various reasons, but it still feels a bit unsatisfactory. Although I was following your model for an executor for a single server/wrapper, this was something I had considered myself anyway as part of the original design for the new API. The general need for this sort of class is for following up an initial request/response with further requests. There are quite a few existing cases within the old codebase where the language server is stored on e.g. a UI class for subsequent requests. We obviously don't want this, which is why I initially proposed using the wrapper (with a public
|
I think there are two issues to resolve:
What do you think? |
Yes, it has its drawbacks, but it has the upside that you can actually tell that the pattern of how to do a remote call to one or many LS, just use
Yes, agree with that. I did not look at the overall code, so I do not know how many of these consumers do we actually have. If they are only a few, I think it is fine, they will just not use these APIs. To me it would not be worth having a new interface. It is similar to using
Yes, I agree with that, that is why in my original version in #396 I did not have it, to me to use
Okay, I see, then we could add to the I can adapt my PR with any of this changes if you like. |
I do not know how much code would profit from a better API, but my feeling now is that not many (as I said above), and that we provide more value by porting the code to the API we have (with minor improvements here and there like what we are doing). |
Actually now that I think about it, is this not a problem with the existing executors ? I can already write
and it is undefined what will happen. I think the LanguageServerProjectExecutor will behave as the |
An interesting point about Just to let you know I have to work on something else for a couple of days, so will follow this up later in the week. |
This covers the main uses for DocumentInfo: following a server request, bundle the document and the server that responded into an object that can follow up further requests. Extracted at reviewer's request from an API port into a standalone PR
85eb2ee
to
a3522bd
Compare
/** | ||
* Creates a new executor from this one, with the same settings and filter, so a further request can be run | ||
*/ | ||
@Override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not remember the details, but I remember that Effective Java (2nd Edition) recommends avoiding clone
and use copy constructors instead, and I think it is a usually accepted pattern.
For example
public static LanguageServerDocumentExecutor forExecutor(final @NonNull LanguageServerDocumentExecutor executor) {
and similar for the other executor types.
Could we follow that recommendation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want, but that's partly because clone
and Clonable
predate generics and are thus broken. I've not actually implemented the 'official' Clonable
interface, just provided a method called clone()
that is generic and thus properly covariantly typed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I see, I have overlooked that. I still think the copy constructor is preferable, as then there is still only pattern to get new executors (constructor) vs two pattern (close + constructor)
* | ||
* Note that versioning support makes the executor classes stateful. Attempting to call request | ||
* methods multiple times on the same executor object will throw an <code>IllegalStateExeception</code>. | ||
* Either call <code>reset()</code> between intervening executions or <code>clone()</code> a fresh object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think we need the reset
method, for the number of times we will actually want to reuse executors, I think just creating a new object should be good enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are perhaps right but I can foresee getting the reverse complaint as well. Certainly it's more of a convenience thing than a necessity thing if clone/copy is implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but since adding is easier than removing, I think we should start small. It is safer :)
return Collections.emptyList(); | ||
} | ||
|
||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should remove this three methods for the time being and add then later if needed, it is really weird that they promote different patterns, like an executor which does not respect the filter.
I would propose that we go without execute
, supports
and getServer
, change a consumers, and the create a new PR showing how those consumers would be indeed simplified.
Could we do that? Adding the methods is indeed simple if really needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The methods were all driven by necessity - the design choices were prompted by the issues I encountered porting code. See for example CodeActionCompletionProposal.apply()
.
I like the idea that if you want to follow up your initial request with a further action - but only if the server supports the more refined capability - then you can call clone().withFilter(...).computeFirst()
and get a result only if the server supports it.
In the case of trying to port the apply method above, there are two criteria for proceeding, only one of which is a server predicate, so just using the filter method doesn't work.
thanks for the newer version of the PR, I have left some comments. |
@@ -268,6 +282,72 @@ public long getStartVersion() { | |||
return this.startVersion; | |||
} | |||
|
|||
public SingleLanguageServerDocumentExecutor toExecutor(final @NonNull LanguageServerWrapper serverWrapper) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This I would also remove, by the way, and keep the API small. It is only a bit of syntactic sugar, and we should only add those if it really pays. I am not sure yet it does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but it does make the api more convenient, especially as a lot of these methods will get used in lambdas. LSP4e is already somewhat rich in lambda functions that ramble on for multiple lines and are hard to read..!
Hi @rubenporras you started re-reviewing before I had a chance to finish typing up my summary of what I had changed and why :-) Just for the record:
I quite like this as a compromise. I know you've already come back with some comments - can I ask you take a look at the other two PRs I have in flight porting stuff to the new API, as that might give some better rationale for the choices? |
Hi @ahmedneilhussain , I think we should discuss the check that deals with the executor classes being stateful in a different thread/PR, I understand the problem but it is indeed unrelated the single-server-document executor, that should make easier to reach a conclusion. I have the feeling that making the executors support versioning looked great, but now that I realize we made the executors stateful I have the feeling it was not good. Regarding the single-server-document executor, I have the feeling that if it inherits from In order to understand better the usage of the new class, I have looked at some code we would migrate and just did that. If we continue doing so I expect at the end I will see how many consumers would profit from the API and think better about what is worth doing in these area. |
I don't think statefulness is a big deal if it's made explicit, and if you get something in return. It's more a problem if it's implicit and sporadic and so people introduce bugs by misusing the class because they're not aware of that aspect of the contract. Lots of classes like eclipse Versioning and cancel support will IMHO make the trade off well worth it, especially as it's trivial to implement |
Hi @ahmedneilhussain , I see your last PR also uses this executor, but only with computeFirst(), which I think it is good enough. Could it be a way forward for the time being that we remove from this PR the What do you think of that? |
Hi @ahmedneilhussain , I have the felling we have done quite well without an executor like this one or the one I proposed in another PR. I already closed my PR, shall we close this one as well? |
Sure @rubenporras I think we can bin this and reinstate one or other idea if a real need arises in future. |
This covers the main uses for DocumentInfo: following a server request, bundle the document and the server that responded into an object that can follow up further requests.
Extracted at reviewer's request from an API port into a standalone PR