-
Notifications
You must be signed in to change notification settings - Fork 770
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
[READY] Java support using jdt.ls #857
Conversation
Just a few comments to start the discussion. Far from a finished review. Also, if we are going to keep Reviewed 54 of 60 files at r1. JAVA_SUPPORT.md, line 246 at r1 (raw file):
How often did it happen to you that the workspace became corrupt? I'm wondering what "often" actually means. JAVA_SUPPORT.md, line 265 at r1 (raw file):
While all this is true and can even be verified in microsoft/language-server-protocol#291, jdt.ls here does the same as the python language server and, if I remember correctly, the same as clangd. A simplified explanation:
Once again there's no guarantee that this will be done for every LSP server we decide to integrate. ycmd/completers/completer.py, line 386 at r1 (raw file):
I'd say this needs another sentence or two. Why most completers don't implement this? Or what completers do implement this. Comments from Reviewable |
Review status: 54 of 60 files reviewed at latest revision, 3 unresolved discussions, some commit checks failed. JAVA_SUPPORT.md, line 246 at r1 (raw file): Previously, bstaletic (Boris Staletic) wrote…
It's happened to me only when multiple instances use the same workspace (i.e. it would happen frequently when running the tests before i went back to ad-hoc workspaces). TBH it's a complete unknown. JAVA_SUPPORT.md, line 265 at r1 (raw file): Previously, bstaletic (Boris Staletic) wrote…
OK so maybe it is a little more standardised. Thanks for the explanation. ycmd/completers/completer.py, line 386 at r1 (raw file): Previously, bstaletic (Boris Staletic) wrote…
Good point indeed. It does. Comments from Reviewable |
FWIW I think the test failures are flakes. JAVA_SUPPORT.md was added mainly for you to comment on :) But we can keep it if we care to. Review status: 54 of 60 files reviewed at latest revision, 3 unresolved discussions, some commit checks failed. Comments from Reviewable |
I started to write a PHP completer relying on the PHP Language Server (PLS) in order to test your LSP implementation. I encountered several issues:
You can find the implementation in this branch. You should be able to test it by adding the In conclusion, this PR looks really promising. Nice work.
We shouldn't ignore the Reviewed 17 of 60 files at r1. build.py, line 100 at r1 (raw file):
Typo in ycmd/completers/java/java_completer.py, line 34 at r1 (raw file):
Parentheses are not needed. ycmd/completers/java/java_completer.py, line 75 at r1 (raw file):
"we has"? Missing word? ycmd/completers/java/java_completer.py, line 83 at r1 (raw file):
We shouldn't talk about Vim. Let's say client instead. ycmd/completers/java/java_completer.py, line 190 at r1 (raw file):
Should be at least ycmd/completers/java/java_completer.py, line 277 at r1 (raw file):
This function doesn't have a ycmd/completers/java/java_completer.py, line 281 at r1 (raw file):
Can be simplified to ycmd/completers/java/java_completer.py, line 369 at r1 (raw file):
I would log this as an error. ycmd/completers/java/java_completer.py, line 370 at r1 (raw file):
This could be rewritten without the if not self._ServerIsRunning():
_logger.warning( 'jdt.ls Language Server failed to start' )
return
_logger.info( 'jdt.ls Language Server started' ) ycmd/completers/java/java_completer.py, line 385 at r1 (raw file):
Should be logged as an error. ycmd/completers/java/java_completer.py, line 397 at r1 (raw file):
Not closing ycmd/completers/java/java_completer.py, line 407 at r1 (raw file):
ycmd/completers/java/java_completer.py, line 410 at r1 (raw file):
Could we define a ycmd/completers/java/java_completer.py, line 433 at r1 (raw file):
ycmd/completers/language_server/language_server_completer.py, line 458 at r1 (raw file):
Appending ycmd/completers/language_server/language_server_completer.py, line 673 at r1 (raw file):
Should be defined at the top of this file. There is no reason to recompute it each time we call this function. ycmd/completers/language_server/language_server_completer.py, line 748 at r1 (raw file):
You are not going to like this but some language servers return ycmd/completers/language_server/language_server_completer.py, line 1305 at r1 (raw file):
The ycmd/tests/java/init.py, line 113 at r1 (raw file):
This function is already defined in Comments from Reviewable |
Thanks so much for trying it out and taking a look! Review status: 57 of 60 files reviewed at latest revision, 22 unresolved discussions, some commit checks failed. build.py, line 100 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 34 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 75 at r1 (raw file): Previously, micbou wrote…
"hash" ycmd/completers/java/java_completer.py, line 83 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 190 at r1 (raw file): Previously, micbou wrote…
Done. Also noted why it is a generic catch. ycmd/completers/java/java_completer.py, line 277 at r1 (raw file): Previously, micbou wrote…
Good point. Removed this from here and also ycmd/completers/java/java_completer.py, line 281 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 369 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 370 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 385 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 397 at r1 (raw file): Previously, micbou wrote…
Well, you could be right that there are still shutdown races. I'll review it again. But I can't use ycmd/completers/java/java_completer.py, line 407 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 410 at r1 (raw file): Previously, micbou wrote…
Renamed to ycmd/completers/java/java_completer.py, line 433 at r1 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 458 at r1 (raw file): Previously, micbou wrote…
OK sure. Done. ycmd/completers/language_server/language_server_completer.py, line 673 at r1 (raw file): Previously, micbou wrote…
I've moved the ls-specific stuff into ycmd/completers/language_server/language_server_completer.py, line 748 at r1 (raw file): Previously, micbou wrote…
You're right. I don't like that because it seems dodgy. I also can't really test it. ycmd/completers/language_server/language_server_completer.py, line 1305 at r1 (raw file): Previously, micbou wrote…
As above, this is not legal in LSP, and doesn't appear to be done by jdt.ls. Perhaps we can defer making this change until we need it ? ycmd/tests/java/init.py, line 113 at r1 (raw file): Previously, micbou wrote…
There is a small difference in the implementation (it uses a different ping timeout), but it also did some other stuff in the past. I have replaced with a call to Comments from Reviewable |
Codecov Report
@@ Coverage Diff @@
## master #857 +/- ##
=========================================
Coverage ? 96.18%
=========================================
Files ? 83
Lines ? 6468
Branches ? 0
=========================================
Hits ? 6221
Misses ? 247
Partials ? 0 |
This is an ungodly amount of work; thanks a ton for doing this! I don't have any time to review this now, I'll have to block off some time on the weekend. I read through the PR description and it all seems very sensible. I do agree that we might want to declare this as experimental in the docs to begin with; should provide @puremourning with the flexibility of breaking backcompat if necessary as the completer develops. I'd say a max 6 month experimental baking period is reasonable. Review status: 54 of 61 files reviewed at latest revision, 5 unresolved discussions, some commit checks failed. Comments from Reviewable |
So somehow the latest patch really broken shutdown on Windows. I'll have to debug it. Who woulda thought shutting down was so hard :) |
116144a
to
507f00d
Compare
@puremourning Did a review pass. This is a criminally big PR! :P Thanks for doing all this work though, it's hugely appreciated. ;) Review status: 51 of 61 files reviewed at latest revision, 18 unresolved discussions. build.py, line 491 at r5 (raw file):
Oh I like this build.py, line 549 at r5 (raw file):
Perhaps ycmd/completers/java/java_completer.py, line 107 at r5 (raw file):
I would rephrase the last sentence to "There can be only one" so as to make a Highlander reference, but that's just me. :D ycmd/completers/language_server/language_server_completer.py, line 159 at r5 (raw file):
startd -> started ycmd/completers/language_server/language_server_completer.py, line 163 at r5 (raw file):
If implementations need to provide some methods, why do those methods need to have the ycmd/completers/language_server/language_server_completer.py, line 183 at r5 (raw file):
You should clarify what the dispatch thread is here (I can guess, but it's not really clear). ycmd/completers/language_server/language_server_completer.py, line 267 at r5 (raw file):
I think our code style is ycmd/completers/language_server/language_server_completer.py, line 419 at r5 (raw file):
"Dispatch" is the preferred spelling, even in British English. http://grammarist.com/spelling/dispatch-despatch/ ycmd/completers/language_server/language_server_completer.py, line 659 at r5 (raw file):
This is a pretty big method; could some parts be extracted into helper methods? ycmd/completers/language_server/language_server_completer.py, line 731 at r5 (raw file):
This item -> completion mapping looks like it could be extracted into a helper method. ycmd/completers/language_server/language_server_completer.py, line 907 at r5 (raw file):
The name is a bit vague; where are the files being refreshed? Why are they being refreshed? What does refresh even mean? Until I read the doc comment below, I didn't even think this would talk to the server. Maybe ycmd/completers/language_server/language_server_completer.py, line 1092 at r5 (raw file):
Vague name. It also contains no verbs so I don't know what is being performed here. ycmd/completers/language_server/language_server_completer.py, line 1157 at r5 (raw file):
Vague name. Rename what to what? Where is it being renamed? Does it send a request to the server or the client? It's not really clear. ycmd/completers/language_server/language_server_completer.py, line 1212 at r5 (raw file):
This is rather big; could it be broken up into helpers? ycmd/completers/language_server/lsapi.py, line 1 at r5 (raw file):
Also, it hasn't been 2016 for a while now. :D Comments from Reviewable |
326bfcc
to
f5f2863
Compare
Thanks so much for looking at it. Review status: 51 of 61 files reviewed at latest revision, 18 unresolved discussions. build.py, line 549 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Yeah, that's better. Done. ycmd/completers/language_server/language_server_completer.py, line 159 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 163 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
I was somewhat torn about this. In the end I went with non-underscore methods are public in the sense that you should call then in the form "my_thing.MyMethod()" and any "protected" method (either used by, or implemented by concrete types) gets an underscore prefix. For example: The user of the Perhaps I should go with On reflection, I didn't completely consistently apply that rule, despite revising it multiple times. What do you think is the "right thing" for these cases:
ycmd/completers/language_server/language_server_completer.py, line 183 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
I've included some of the text from the PR description in the doc string explaining what the dispatch thread is and made this more explicit. ycmd/completers/language_server/language_server_completer.py, line 267 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
yes it is, fixed. ycmd/completers/language_server/language_server_completer.py, line 419 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Moreover, we've agreed that we should use US English in all the code and comments! Fixed. ycmd/completers/language_server/language_server_completer.py, line 659 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
As below, I've extracted a piece, but this whole "resolve and then mess around with completions" stuff is just frustratingly complicated due to the protocol. Happy to iterate on this if it needs to go further. ycmd/completers/language_server/language_server_completer.py, line 731 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Done. The rest of the method is 40 lines removing comments and empty lines. Seem ok ? ycmd/completers/language_server/language_server_completer.py, line 907 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Went with ycmd/completers/language_server/language_server_completer.py, line 1092 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Changed to ycmd/completers/language_server/language_server_completer.py, line 1157 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Changed to ycmd/completers/language_server/language_server_completer.py, line 1212 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
I pulled out the validation of the textEdit as that is a chunk of annoying code that distracts from the important stuff. ycmd/completers/language_server/lsapi.py, line 1 at r5 (raw file):
Tell me about it. Then again, it was 2016 when I wrote it, and first pushed it to github, so strictly that's from when I (we) claim copyright on it, and is also the year of first "publication". Then again, IANAL as a wise man once said. Then again, perhaps the year it is merged is the year of "first publication". I really don't know.
I renamed the module to Comments from Reviewable |
Review status: 50 of 61 files reviewed at latest revision, 17 unresolved discussions. ycmd/completers/completer.py, line 386 at r1 (raw file): Previously, puremourning (Ben Jackson) wrote…
Done. I also tidied up the API and updated the And fixed a bug refactoring error. Yeah, that too. Comments from Reviewable |
Review status: 46 of 66 files reviewed at latest revision, 8 unresolved discussions, some commit checks failed. ycmd/completers/language_server/language_server_completer.py, line 163 at r5 (raw file): Previously, puremourning (Ben Jackson) wrote…
Well, the WRT underscore, I understand your reasoning. I thought that might have been it. To think of it in terms of other OOP languages like say Java, a method that needs to be implemented in a subclass is pretty much always public. Java won't even let you create a There's a fairly strong culture of "public" meaning "this is the interface/API surface of this class" (which does include methods meant to be overridden), which is a wider definition than "clients are allowed to call this." Prominent voices in Java land also claim that I think ultimately it would be very confusing to readers to see private (i.e. underscore) methods be overridden. Maybe I'm wrong, but it did make me go "wait, wtf" when I first saw it. :) ycmd/completers/language_server/language_server_completer.py, line 731 at r5 (raw file): Previously, puremourning (Ben Jackson) wrote…
It's ok (but barely) for now; I'm more concern with this method getting more code added to it in the future and becoming a monster. It has the potential. Keep an eye on it in future PRs. If it gets any more code added to it, it really needs splitting up. ycmd/completers/language_server/language_server_completer.py, line 907 at r5 (raw file): Previously, puremourning (Ben Jackson) wrote…
Nice name! ycmd/completers/language_server/language_server_protocol.py, line 1 at r5 (raw file):
I feel it's important to point out it's not "we" who have copyright on your changes, only you do. :) The whole "ycmd contributors" thing does not change who owns the copyright to this code (only you do), it merely makes it so that we don't have to list each and every single contributor in the header. The changelog still has authorship info which is why we can do this. Comments from Reviewable |
ed2fdcb
to
f1d8a59
Compare
Thanks again for the thoughts! Review status: 46 of 66 files reviewed at latest revision, 6 unresolved discussions. ycmd/completers/language_server/language_server_completer.py, line 163 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
OK thanks! I change it to: non-private (i.e. no leading I also made everything private that isn't currently relied upon to be public. This restricts the API to "what's required" and we can open it up if subsequent servers require it. I decided to completely ignore the thing i just learned about python name mangling and methods starting (but not ending) with ycmd/completers/language_server/language_server_completer.py, line 731 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
I looked again and decided that you're right; there are 2 other multi-call-blocks which could be broken out. the entire method without comments is now: def ResolveCompletionItems( self, items, request_data ):
do_resolve = self._ShouldResolveCompletionItem( )
completions = list()
start_codepoints = list()
min_start_codepoint = request_data[ 'start_codepoint' ]
for item in items:
if do_resolve:
item = self._ResolveCompletionItem( item )
try:
( insertion_text, fixits, start_codepoint ) = (
_InsertionTextForItem( request_data, item ) )
except IncompatibleCompletionException:
_logger.exception( 'Ignoring incompatible completion suggestion '
'{0}'.format( item ) )
continue
min_start_codepoint = min( min_start_codepoint, start_codepoint )
completions.append( _CompletionItemToCompletionData( insertion_text,
item,
fixits ) )
start_codepoints.append( start_codepoint )
if ( len( completions ) > 1 and
min_start_codepoint != request_data[ 'start_codepoint' ] ):
return _FixUpCompletionPrefixes( completions,
start_codepoints,
request_data,
min_start_codepoint )
request_data[ 'start_codepoint' ] = min_start_codepoint
return completions ycmd/completers/language_server/language_server_protocol.py, line 1 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
Heh, ok, sure. Then again, you gotta be some level of nuts to copy my code :) Comments from Reviewable |
Here are my thoughts after playing around with a python language server. First, jdt.ls probably wasn''t the best choice to showcase ycmd's LSP support. To be fair, jdt.ls was chosen for a completely different reason. What was working:
Bottom line is that it was a neat API to work with. Reviewed 3 of 61 files at r1, 5 of 7 files at r2, 3 of 4 files at r3, 2 of 2 files at r5, 1 of 6 files at r6, 15 of 16 files at r7. JAVA_SUPPORT.md, line 224 at r3 (raw file):
How does long-polling mechanism solve this problem? Later in the code a comment says it polls once every hour. third_party/eclipse.jdt.ls, line 1 at r7 (raw file):
This is my biggest concern about this PR. It had to be changed more than once and we haven't even had a single LGTM. ycmd/completers/java/java_completer.py, line 107 at r5 (raw file): Previously, Valloric (Val Markovic) wrote…
One more in favour of the Highlander reference. Comments from Reviewable |
Review status: 64 of 66 files reviewed at latest revision, 6 unresolved discussions, some commit checks failed. ycmd/completers/language_server/language_server_completer.py, line 731 at r5 (raw file): Previously, puremourning (Ben Jackson) wrote…
👍 Comments from Reviewable |
Reviewed 36 of 61 files at r1, 4 of 7 files at r2, 1 of 1 files at r4, 2 of 2 files at r5, 4 of 6 files at r6, 13 of 16 files at r7. JAVA_SUPPORT.md, line 224 at r3 (raw file):
In the client. README.md, line 93 at r7 (raw file):
The Java completer is not mentioned. examples/example_client.py, line 489 at r7 (raw file):
Are we polling 6 or 10 times? examples/example_client.py, line 508 at r7 (raw file):
Same comment. ycmd/completers/completer.py, line 154 at r6 (raw file):
ycmd/completers/java/java_completer.py, line 358 at r7 (raw file):
Using ycmd pid in the logfile name seems out of place. Not even sure why we need to put ycmd/completers/language_server/language_server_completer.py, line 1305 at r1 (raw file): Previously, puremourning (Ben Jackson) wrote…
Yes, this can wait. ycmd/completers/language_server/language_server_completer.py, line 85 at r6 (raw file):
Missing ycmd/completers/language_server/language_server_completer.py, line 119 at r6 (raw file):
Missing ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file):
Why is this indented with 4 spaces? ycmd/completers/language_server/language_server_completer.py, line 160 at r6 (raw file):
Should ycmd/completers/language_server/language_server_completer.py, line 172 at r6 (raw file):
Missing ycmd/completers/language_server/language_server_completer.py, line 442 at r6 (raw file):
Parentheses are not needed. ycmd/completers/language_server/language_server_completer.py, line 603 at r6 (raw file):
Typo in ycmd/completers/language_server/language_server_completer.py, line 756 at r6 (raw file):
Typo in ycmd/completers/language_server/language_server_completer.py, line 930 at r6 (raw file):
Missing ycmd/completers/language_server/language_server_completer.py, line 973 at r6 (raw file):
Missing ycmd/completers/language_server/language_server_completer.py, line 1042 at r6 (raw file):
Missing ycmd/completers/language_server/language_server_completer.py, line 54 at r7 (raw file):
I feel we should add ycmd/completers/language_server/language_server_completer.py, line 299 at r7 (raw file):
The protocol supports a number for the ycmd/completers/language_server/language_server_completer.py, line 311 at r7 (raw file):
If the assertion fails, the thread will be killed and the completer won't work anymore. I think we should do our best to always keep the connection alive. I would replace the assertion with a ycmd/completers/language_server/language_server_completer.py, line 360 at r7 (raw file):
These parentheses are not needed. ycmd/completers/language_server/language_server_completer.py, line 458 at r7 (raw file):
Same comment as above about the ycmd/completers/language_server/language_server_completer.py, line 600 at r7 (raw file):
Should be named ycmd/completers/language_server/language_server_completer.py, line 603 at r7 (raw file):
ycmd/completers/language_server/language_server_completer.py, line 761 at r7 (raw file):
Parentheses around ycmd/completers/language_server/language_server_completer.py, line 994 at r7 (raw file):
ycmd/completers/language_server/language_server_completer.py, line 1019 at r7 (raw file):
ycmd/completers/language_server/language_server_completer.py, line 1198 at r7 (raw file):
Two blank lines. ycmd/completers/language_server/language_server_completer.py, line 1267 at r7 (raw file):
We should avoid ycmd/completers/language_server/language_server_completer.py, line 1272 at r7 (raw file):
ycmd/completers/language_server/language_server_completer.py, line 1325 at r7 (raw file):
Parentheses are not needed. ycmd/completers/language_server/language_server_completer.py, line 1373 at r7 (raw file):
Both ycmd/completers/language_server/language_server_completer.py, line 1386 at r7 (raw file):
No logging here? Also, ycmd/completers/language_server/language_server_completer.py, line 1423 at r7 (raw file):
Should we log something here? ycmd/completers/language_server/language_server_completer.py, line 1446 at r7 (raw file):
Extra space before parenthesis. ycmd/completers/language_server/language_server_completer.py, line 1481 at r7 (raw file):
This doesn't look like a good reason. We can always sort the response in the tests. ycmd/completers/language_server/language_server_protocol.py, line 253 at r7 (raw file):
According to the protocol, we should use ycmd/tests/clang/init.py, line 31 at r7 (raw file):
Let's replace all instances of ycmd/tests/java/diagnostics_test.py, line 27 at r7 (raw file):
Parentheses are not needed. ycmd/tests/java/diagnostics_test.py, line 38 at r7 (raw file):
Can be rewritten on one line (without the parentheses). ycmd/tests/java/get_completions_test.py, line 37 at r7 (raw file):
Parentheses are not needed. Comments from Reviewable |
Reviewed 1 of 6 files at r6, 1 of 16 files at r7. JAVA_SUPPORT.md, line 224 at r3 (raw file): Previously, micbou wrote…
Thanks for the explanation. ycmd/completers/language_server/language_server_completer.py, line 1481 at r7 (raw file): Previously, micbou wrote…
This was added after I wrote the test for At the time I did not quite understand the reasoning, so @puremourning should be able to explain why tthis was done this way. Comments from Reviewable |
2b266de
to
2471e31
Compare
Review status: 59 of 68 files reviewed at latest revision, 44 unresolved discussions. README.md, line 93 at r7 (raw file): Previously, micbou wrote…
Done. examples/example_client.py, line 489 at r7 (raw file): Previously, micbou wrote…
Erm. 5 it would seem! examples/example_client.py, line 508 at r7 (raw file): Previously, micbou wrote…
Erm. 5 it would seem! third_party/eclipse.jdt.ls, line 1 at r7 (raw file): Previously, bstaletic (Boris Staletic) wrote…
I don't see that we have any other choice. I asked about stable releases in the chat but didn't really get a straight answer. It's possible that the problems before were transient. They seem to at least be using stable tags now, which is what we are pegging to. ycmd/completers/completer.py, line 154 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 107 at r5 (raw file): Previously, bstaletic (Boris Staletic) wrote…
I'm not confident enough that the jdt.ls people and/or the dark magic of maven wouldn't break the declaration, which would be tragic for more than one reason. ycmd/completers/java/java_completer.py, line 358 at r7 (raw file): Previously, micbou wrote…
My assumption was that if we didn't use this, then wouldn't we get collisions in the case of multiple Vim instances all running an instance of jdt.ls? We often use the port of the server, but as we're not using TCP, we don't have a port to use. Using the ycmd pid at least means there's only one process writing to the file.... But on reflection, the return from I kept stderr in the name because it makes it clear that this is the server's standard error, as opposed to its log file (which is ycmd/completers/language_server/language_server_completer.py, line 85 at r6 (raw file): Previously, micbou wrote…
Done. I ran through everything that Vim reports as misspelled in this file and the other main python files. I didn't do all of the tests, I confess. ycmd/completers/language_server/language_server_completer.py, line 119 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 160 at r6 (raw file): Previously, micbou wrote…
Nope. Done. ycmd/completers/language_server/language_server_completer.py, line 172 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 442 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 603 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 756 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 930 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 973 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1042 at r6 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 54 at r7 (raw file): Previously, micbou wrote…
I considered that, but decided that it was yet another (mandatory) API change, and the YCM code for this is quite fiddly (with all of the filtering stuff). It seemed easier to keep the API the same. But sure, why not. ycmd/completers/language_server/language_server_completer.py, line 299 at r7 (raw file): Previously, micbou wrote…
At least one server which I tested completely broke if the ID wasn't a string. There's no reason for us to be particular (we don't actually really care about what type the ID is, and a number is arbitrary). Given that this works, and we're already working around at least 3 other jdt.ls bugs, I think this is probably ok? ycmd/completers/language_server/language_server_completer.py, line 311 at r7 (raw file): Previously, micbou wrote…
This code happens in a bottle thread, so bottle will catch the exception. ycmd/completers/language_server/language_server_completer.py, line 360 at r7 (raw file): Previously, micbou wrote…
OK fine ycmd/completers/language_server/language_server_completer.py, line 458 at r7 (raw file): Previously, micbou wrote…
I think if we receive a response to a request that we didn't make, then it is perfectly reasonable to kill the dispatch thread and for it to stop working. It's highly unlikely that this is a recoverable situation - it means there's a serious protocol error or a serious bug. I'd honestly prefer to know about it at the point it is detected rather than at some point later. This does indeed kill the message pump, and so stuff will just hang. I've added a catch and log to the dispatch thread main loop to at least signpost this in the log and give some advice to fix it. We can perhaps, if this happens often, change this to automatically restart the connection, but as I said i would like to know about the bugs for now. ycmd/completers/language_server/language_server_completer.py, line 600 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 603 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 761 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 994 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1019 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1198 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1267 at r7 (raw file): Previously, micbou wrote…
I tend to disagree on this point, and agree (mostly) with what is said (at the start) here: https://wiki.python.org/moin/UsingAssertionsEffectively If it gets optimised out by some JIT optimiser, then so be it, the assertions have no side-effects, so there's no harm no foul. IMO anyway. ycmd/completers/language_server/language_server_completer.py, line 1272 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1325 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1373 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1386 at r7 (raw file): Previously, micbou wrote…
No logging because it's a legit scenario. I was sure I'd written a comment about this, but clearly not here. filename is defined because the Anyway I added both a comment and log. ycmd/completers/language_server/language_server_completer.py, line 1423 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1446 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 1481 at r7 (raw file): Previously, bstaletic (Boris Staletic) wrote…
It's crucially important that the FixIt chunks are applied in a particular sequence. I felt that giving users of ycmd API a guaranteed stable interface (file-wise) seemed reasonable. I'd like to understand your objection to this before I change it as it is a lot of work, and as @bstaletic said, I spent days on this area getting it solid and well tested :/ ycmd/completers/language_server/language_server_protocol.py, line 253 at r7 (raw file): Previously, micbou wrote…
it appears they have changed the protocol since I wrote this. i also think it is correct to be explicit as the protocol says "only supported right now" and hasn't been shy about changing it! At least one completer server that I tested required this, and another, which I fixed, barfed on it. Unfortunately I wasn't able to easily see if VSCode sends this header or not, as there doesn't appear to be a full protocol tracing facility (and stdio can't be snooped aiui). Anyway I've removed the header because it works, we can add it if it breaks. ycmd/tests/clang/init.py, line 31 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/tests/java/diagnostics_test.py, line 27 at r7 (raw file): Previously, micbou wrote…
Fine ycmd/tests/java/diagnostics_test.py, line 38 at r7 (raw file): Previously, micbou wrote…
Done. ycmd/tests/java/get_completions_test.py, line 37 at r7 (raw file): Previously, micbou wrote…
Done. Comments from Reviewable |
Reviewed 1 of 6 files at r6, 11 of 11 files at r8. third_party/eclipse.jdt.ls, line 1 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
Okay, guess we just have to accept that our "old commits" might break eventually. Which kind of breaks the claim that every commit is a release. Maybe we should consider something like Comments from Reviewable |
Reviewed 1 of 6 files at r6, 8 of 11 files at r8. ycmd/completers/java/java_completer.py, line 487 at r8 (raw file):
Unneeded parentheses. ycmd/completers/java/java_completer.py, line 500 at r8 (raw file):
ycmd/completers/java/java_completer.py, line 512 at r8 (raw file):
Why a ycmd/completers/java/java_completer.py, line 514 at r8 (raw file):
ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file): Previously, puremourning (Ben Jackson) wrote…
Still indented with 4 spaces instead of 2. ycmd/completers/language_server/language_server_completer.py, line 299 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
Fair enough. ycmd/completers/language_server/language_server_completer.py, line 311 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
Right. I thought this was in the same thread as the other ycmd/completers/language_server/language_server_completer.py, line 458 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
I experienced this while implementing RLS (Rust Language Server) support. The server is incorrectly returning two responses with the same id. It took me some time to find the cause as there was nothing in the logs. Now that we are logging the assertion, I am fine with keeping the Just one thing. Can we introduce an intermediate variable ycmd/completers/language_server/language_server_completer.py, line 1267 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
That link changed my mind. Let's keep the ycmd/completers/language_server/language_server_completer.py, line 1481 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
I missed the first sentence in the comment. I thought the only reason to sort was to help writing the tests. I am fine with sorting by filename. ycmd/completers/language_server/language_server_completer.py, line 1261 at r8 (raw file):
Where do we tell the server that we don't support snippets? I can't find it. ycmd/completers/language_server/language_server_protocol.py, line 253 at r7 (raw file): Previously, puremourning (Ben Jackson) wrote…
I am almost certain that VS Code doesn't send this header because RLS doesn't support it (I reported the issue on RLS tracker) and there is a VS Code extension for it. Comments from Reviewable |
Review status: all files reviewed at latest revision, 11 unresolved discussions, some commit checks failed. ycmd/completers/java/java_completer.py, line 514 at r8 (raw file):
I know this was my code more or less, but... There's return responses.BuildDetailedInfoResponse( get_doc_java ) ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file): Previously, micbou wrote…
To calrify, @micbou is refering to the text inside the tripple quotes. Comments from Reviewable |
Review status: all files reviewed at latest revision, 10 unresolved discussions, some commit checks failed. ycmd/completers/java/java_completer.py, line 514 at r8 (raw file): Previously, bstaletic (Boris Staletic) wrote…
Right. I noticed that we were removing trailing characters twice but forgot to mention it. ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file): Previously, bstaletic (Boris Staletic) wrote…
Yes, the docstring. Comments from Reviewable |
Review status: 64 of 69 files reviewed at latest revision, 10 unresolved discussions. third_party/eclipse.jdt.ls, line 1 at r7 (raw file): Previously, bstaletic (Boris Staletic) wrote…
They just today announced a "stable" repo. This is about the 2nd such repo, so i'm keeping an eye on it. At the least we could just wget the tarball and extract it, rather than build it. But I think this is part of the experimental parts - the server is still pre v1.0.0 ycmd/completers/java/java_completer.py, line 487 at r8 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 500 at r8 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 512 at r8 (raw file): Previously, micbou wrote…
Dunno; went with RuntimeError. We should really not throw exceptions for these things! It's really not an error. ycmd/completers/java/java_completer.py, line 514 at r8 (raw file): Previously, micbou wrote…
Done. ycmd/completers/java/java_completer.py, line 514 at r8 (raw file): Previously, bstaletic (Boris Staletic) wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file): Previously, bstaletic (Boris Staletic) wrote…
It is indented 2 (i.e. once) from the Is it a problem ? It seems pretty readable to me. ycmd/completers/language_server/language_server_completer.py, line 458 at r7 (raw file):
Yes, we should totally do that. Done! ycmd/completers/language_server/language_server_completer.py, line 1261 at r8 (raw file): Actually, we don't specify any client capabilities, which means:
So it would be illegal to supply snippets, as we don't set the the I updated the comment to be more clear. Comments from Reviewable |
Review status: 64 of 69 files reviewed at latest revision, 10 unresolved discussions. ycmd/completers/language_server/language_server_completer.py, line 156 at r6 (raw file): Previously, puremourning (Ben Jackson) wrote…
Nvm, i changed it to be consistent with the others. Comments from Reviewable |
Review status: 64 of 69 files reviewed at latest revision, 10 unresolved discussions. ycmd/completers/java/java_completer.py, line 514 at r8 (raw file): Previously, puremourning (Ben Jackson) wrote…
Done. Comments from Reviewable |
Close the connection in the handler thread to avoid race condition. Test failed shutdown
Increase the timeout for the shutdown tests for our CI environment Test receive_messages handler without completer support Fix project detection tests Additional tests for things we can't easily test in the CI with real server
Rename lsapi to language_server_protocol Tidy build.py Better names for subcommand handlers Use snake case for variables Clarify the threading setup for the language server connection Despatch -> Dispatch Better name for _RefreshFiles Refactor large completion item methods Update API documentation for long poll, and fix the nonblocking pending messages handler rename textEdit to text_edit for coding style Tracing shutdown to help post mortem diagnostics Update copyright year Move all exceptions to he top of the module Public or API methods are not private. All other methods are private More refactoring of completion item resolution code Compute whether or not to resolve completions statically Remove bare except Mention jdt.ls in the ycmd README Fix up comment in example client Typos and stylistic changes Log when we get errors Don't include the optional header Remove import hack laziness for TemporaryClangTestDir Remove extraneous parentheses More trivial code tidy Use correct GoTo response and errors Don't convert the id to string more than once Clarify client capabilitites Add INFORMATION and HINT to the ycmd API rather than mapping it Use 'Unkonwn type' like clang completer
Join the dispatch thread after closing sockets Don't forcefully kill the server as this causes issues on Windows Ensure that we don't start (and not stop) a JediHTTP instance in shutdown tests Use SharedYcmd for handler tests Properly namespace server management tests
This required a number of things, not least implementing a proper model of the server file state and using that to reduce the number of didClose/didChange events to send. This fixes the interpretation of the file_data: only modified buffers are included, so we must read the contents of non-modified (but open) buffers from disk. Finally, we also move to using binary packages to improve the build times, and tidy up the third party area.
Since we start the server on the FileReadyToParse event, the client might send the initial poll request before we've attempted to start the server. So in that case we wait at least one poll interval for the initialize sequence to complete before starting the poll loop.
Per the language server protocol, we need to count UTF16 code units.
On larger projects and slower systems, resolving many items can be very costly, and not really all that useful.
83ea5d7
to
e03836f
Compare
Review status: all files reviewed at latest revision, 7 unresolved discussions. .circleci/install_dependencies.sh, line 111 at r48 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 803 at r48 (raw file): Previously, bstaletic (Boris Staletic) wrote…
It just doesn't include the docs and additional fixits, which isn't a big deal. Completion strings still mainly work. ycmd/completers/language_server/language_server_completer.py, line 891 at r48 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 924 at r48 (raw file): Previously, micbou wrote…
Done. ycmd/completers/language_server/language_server_completer.py, line 929 at r48 (raw file): Previously, micbou wrote…
Done. ycmd/tests/language_server/language_server_protocol_test.py, line 1 at r48 (raw file): Previously, micbou wrote…
Done. ycmd/tests/language_server/language_server_protocol_test.py, line 193 at r48 (raw file): Previously, micbou wrote…
Done. Comments from Reviewable |
@zzbot r+ Reviewed 3 of 3 files at r49. Comments from Reviewable |
📌 Commit e03836f has been approved by |
[READY] Java support using jdt.ls OK take a deep breath, this is a monster PR. First and foremost, massive thanks to @bstaletic and @micbou for helping not only with the development and testing, but also for moral support! # Overview This PR implements native support in ycmd for the Java language, based on [jdt.ls][]. In summary, the following key features are supported: * Installation of jdt.ls (built from source with `build.py`) * Management of the jdt.ls server instance, projects etc. * A generic (ish) implementation of a [Language Server Protocol][lsp] client so far as is required for jdt.ls (easily extensible to other engines) * Support for the following Java semantic engine features: * Semantic code completion, including automatic imports * As-you-type diagnostics * GoTo including GoToReferences * FixIt * RefactorRename * GetType * GetDoc See the [trello board][trello] for a more complete picture. # Show me the demo, already. OK sure. ## Startup and completion ![ycm-java-startup-debug](https://user-images.githubusercontent.com/10584846/31523935-54ddcdc2-afae-11e7-9ea5-766a8fde3279.gif) ## Rename ![ycm-java-refactorrename](https://user-images.githubusercontent.com/10584846/31523924-4dd8fce0-afae-11e7-9859-70ea162ced31.gif) ## FixIt ![ycm-java-fixit](https://user-images.githubusercontent.com/10584846/31523868-0c3fd362-afae-11e7-9d5f-f1448aee8fcb.gif) ## GoToReferences ![ycm-java-gotorefs](https://user-images.githubusercontent.com/10584846/31523876-13ae72c0-afae-11e7-9ef0-7804b07c2f04.gif) # Why is this necessary and useful? The current state of the plugin for YCM support for Java is basically [eclim][] or [javacomplete2][], neither of which work perfectly with ycmd, and neither of which are quite as well integrated as we might like. ## eclim Eclim is actually pretty good and I have used it for a long time. However, the experience isn't optimal for YCM: ### Pros * Pretty good project creation, management functions * Lots of languages (supports more than just Java) * Lots of features (supports nearly all the standard YCMD commands) * New class creation, and lots of other stuff like that ### Cons * It's a pain to set up (I know of only 1 other person at my company who has successfully got it working), requiring a Vimball, installation into an existing eclipse installation, manually starting the server, and other fiddly stuff. * Diagnostics are delivered via Syntastic, so block the UI on save, and at a number of other times. * Requires eclipse installation and understanding ## javacomplete2 I have not extensively used javacomplete2, but I know there have been issues reported that its omnifunc doesn't always play nice with ycmd. ### Pros? * Very simple to get going * Seems to give completion suggestions reasonably well ### Cons? * Lacks features * No diagnostics ? * Lots of vimscript (java parser in vimscript?) ## Notable mentions * [vim-lsp][] seems to be a plugin for generic [LSP][lsp] support. I haven't tried it but my experience with jdt.ls is that there is no way to make a fully working LSP client without server-specific knowledge. I don't know if this works with jdt.ls # Why jdt.ls? I tried 2 different Java LSP engines: * [vscode-javac][]: javac (the Java compiler) is the backend. Actually, this is a nice and simple server and has decent features. * [jdt.ls][]: Eclipse JDT as a server daemon. This appears to be the most popular Java plugin for [VSCode][], and has the full power of RedHat and Microsoft investing in bringing JDT (the eclipse Java engine) to VSCode and the LSP-using community. Initially, for its simplicity, I was [drawn to vscode-javac](puremourning@9180e96) for its simplicity and ease of integration with ycmd. However, I also implemented [jdt.ls][] and found this, while more work, to be a superior experience. It also has a very active development community, which bodes well for the future. # Why such a big PR, man. Come on! I'm sorry. I really really am. I honestly tried to rebase and rewrite the ~150 commits over the last 1 year or so, but as with many such things, a lot of the functionality that would make "Phase 1" ended up relying on functionality and test framework that came in "Phase N". So I'm dumping this on you in one unwieldy heap and hoping that you can find it in your hearts to forgive me. I honestly spent hours trying :( # OK, but come on, give us a hand... Sure thing! I've tried to give below a "review guide" to help you get your heads around what's going on an why some of the design decisions where made. ## Overall design/goals Key goals: 1. Support Java in ycmd and YCM; make it good enough to replace eclim and javacomplete2 for most people 2. Make it possible/easy to support other [lsp][] servers in future (but, don't suffer from yagni); prove that this works. An overview of the objects involved can be seen on [this card][design]. In short: * 2 classes implement the language server protocol in the `language_server_completer.py` module: * `LanguageServerConnection` - an abstraction of the comminication with the server, which may be over stdio or any number of TCP/IP ports (or a domain socket, etc.). Only a single implementation is included (stdio), but [implementations for TCP/IP](puremourning@f3cd062) were written originally and dropped in favour of stdio's simplicity. * `LanguageServerCompleter` - an abstract base for any completer based on LSP, which implements as much standard functionality as possible including completions, diagnostics, goto, fixit, rename, etc. * The `java_completer` itself implements the `LanguageServerCompleter`, boots the jdt.ls server, and instantiates a `LanguageServerConnection` for communication with jdt.ls. The overall plan and some general discussion around the project can be found on the [trello board][trello] I used for development. The proof? I implemented a fully working [clangd][] [completer for ycmd][clangd-completer] in about 30 minutes. The only wrinkles were clangd bugs! ## Threads, why oh why so many threads. OK chill. It's not thhaartd. LSP is by its nature an asyncronous protocol. There are request-reply like `requests` and unsolicited `notifications`. Receipt of the latter is mandatory, so we cannot rely on there being a `bottle` thread executing a client request. So we need a message pump and despatch thread. This is actually the `LanguageServerConnection`, which implements `thread`. It's main method simply listens on the socket/stream and despatches complete messages to the `LanguageServerCompleter`. It does this: * For `requests`: similarly to the TypeScript completer, using python `event` objects, wrapped in our `Response` class * For `notifications`: via a synchronised `queue`. More on this later. A very poor representation of this is on the "Requests and notifications" page of the [design][], including a rough sketch of the thread interaction. ### Some handling is done in the message pump. That's right. There are certain notifications which we have to handle when we get them, such as: * Initialisation messages * Diagnostics In these cases, we allow some code to be executed inline within the message pump thread, as there is no other thread guaranteed to execute. These are handled by callback functions and mutexes. ## Startup sequence See the 'initialisation sequence' tab on the [design][] for a bit of background. In standard LSP, the initialisation sequence consists of an initialise request-reply, followed by us sending the server an initialised notification. We must not send any other requests until this has completed. An additional wrinkle is that jdt.ls, being based on eclipse has a whole other initialisation sequence during which time it is not fully functional, so we have to determine when that has completed too. This is done by jdt.ls-specific messages and controls the `ServerIsReady` response. In order for none of these shenanigans to block the user, we must do them all asynchronously, effectively in the message pump thread. In addition, we must queue up any file contents changes during this period to ensure the server is up to date when we start processing requests proper. This is unfortunately complicated, but there were early issues with really bad UI blocking that we just had to get rid of. ## Completion foibles Language server protocol requires that the client can apply textEdits, rather than just simple text. This is not an optional feature, but ycmd clients do not have this ability. The protocol, however, restricts that the edit must include the original requested completion position, so we can perform some simple text manipulation to apply the edit to the current line and determine the completion start column based on that. In particular, the jdt.ls server returns textEdits that replace the entered text for import completions, which is one of the most useful completions. We do this super inefficiently by attempting to normalise the TextEdits into insertion_texts with the same start_codepoint. This is necessary particularly due to the way that eclipse returns import completions for packages. We also include support for "additionalTextEdits" which allow automatic insertion of, e.g., import statements when selecting completion items. These are sent on the completion response as an additional completer data item called 'fixits'. The client applies the same logic as a standard FixIt once the selected completion item is inserted. ## Diagnostics foibles Diagnostics in LSP are delivered asynchronously via `notifications`. Normally, we would use the `OnFileReadyToParse` response to supply diagnostics, but due to the lag between refreshing files and receiving diagnostics, this leads to a horrible user experience where the diagnostics always lag one edit behind. To resolve this, we use the long-polling mechanism added here (`ReceiveMessages` request) to return diagnostics to the client asynchronously. We deliver asynchronous diagnostics to the client in the same way that the language server does, i.e. per-file. The client then fans them out or does whatever makes sense for the client. This is necessary because it isn't possible to know when we have received all diagnostics, and combining them into a single message was becoming clunky and error prone. In order to be relatively compatible with other clients, we also return diagnostics on the file-ready-to-parse event, even though they might be out of date wrt the code. The client is responsible for ignoring these diagnostics when it handles the asynchronously delivered ones. This requires that we hold the "latest" diagnostics for a file. As it turns out, this is also required for FixIts. ## Projects jdt.ls is based on eclipse. It is in fact an eclipse plugin. So it requires an eclipse workspace. We try and hide this by creating an ad-hoc workspace for each ycmd instance. This prevents the possibility of multiple "eclipse" instances using the same workspace, but can lead to unreasonable startup times for large projects. The jdt.ls team strongly suggest that we should re-use a workspace based on the hash of the "project directory" (essentially the dir containing the project file: `.project`, `pom.xml` or `build.gradle`). They also say, however, that eclipse frequently corrupts its workspace. So we have a hidden switch to re-use a workspace as the jdt.ls devs suggest. In testing at work, this was _mandatory_ due to a slow SAN, but at home, startup time is not an issue, even for large projects. I think we'll just have to see how things go to decide which one we want to keep. ## Subcommand foibles ### GetDoc/GetType There is no GetType in LSP. There's only "hover". The hover response is hilariously server-specific, so in the base `LanguageServerCompleter` we just provide the ability to get the `hover` response and `JavaCompleter` extracts the appropriate info from there. Thanks to @bstaletic for this! ### FixIt FixIts are implemented as code actions, and require the diagnostic they relate to to be send from us to the server, rather than just a position. We use the stored diags and find the nearest one based on the `request_data`. What's worse is that the LSP provides _no documentation_ for what the "Code action" response should be, and it is 100% implementation-specific. They just have this `command` abstraction which is basically "do a thing". From what I've seen, most servers just end up with either a `WorkspaceEdit` or a series of `TextEdits`, which is fine for us as that's what ycmd's protocol looks like. The solution is that we have a callback into the `JavaCompleter` to handle the (custom) `java.apply.workspaceEdit` "command". ### GoToReferences Annoyingly, jdt.ls sometimes returns references to .class files within jar archives using a custom `jdt://` protocol. We can't handle that, so we have to dodge and weave so that we don't crash. ### Stopping the server Much like the initialisation sequence, the LSP shutdown sequence is a bit fiddly. 2 things are required: 1. A `shutdown` request-reply. The server tides up and _prepares to die!_ 2. An `exit` notification. We tell the server to die. This isn't so bad, but jdt.ls is buggy and actually dies without responding to the `shutdown` request. So we have a bunch of code to handle that and to ensure that the server dies eventually, as it had a habbit of getting stuck running, particularly if we threw an exception. # Closing remarks Phew. That was a lot of caveats! I have personally used this quite a lot now, and it has proven to be really useful. In particular the full index and GoToReferences, etc. I think the tests are comprehensive, but there is probably more work to do on coverage. Some of it will be tricky (particularly exceptions around `jdt:` and other edge cases in jdt.ls that I've come across). ## Ship it as experimental ? Due to the rapid pace of development of jdt.ls and the scale of this change, one thought I had was to mark Java support in YCM as _exprrimental_ and gather feedback via an open github issue. I'm certain there will be issues, and preferences, etc. so this might be a good way to tackle that. # Where's the client PR ? I haven't finished polishing the client yet, but you can get use it by checking out [my `language-server-java` branch of YCM][client]. [jdt.ls]: https://github.com/eclipse/eclipse.jdt.ls [lsp]: https://github.com/Microsoft/language-server-protocol/ [eclim]: http://eclim.org [javacomplete2]: https://github.com/artur-shaik/vim-javacomplete2 [vscode-javac]: https://github.com/georgewfraser/vscode-javac [VSCode]: https://code.visualstudio.com [design]: https://trello.com/c/78IkFBzp [trello]: https://trello.com/b/Y6z8xag8/ycm-java-language-server [client]: https://github.com/puremourning/YouCompleteMe/tree/language-server-java [clangd]: https://clang.llvm.org/extra/clangd.html [clangd-completer]: puremourning@787cc88 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/ycmd/857) <!-- Reviewable:end -->
💔 Test failed - status-appveyor |
☀️ Test successful - status-appveyor, status-travis |
[READY] Update API documentation The website containing the API docs wasn't updated when merging PR #857 (the `/receive_messages` handler is missing). This PR fixes some typos, renames `openapi.yaml` to `openapi.yml`, and updates the website. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/ycmd/911) <!-- Reviewable:end -->
[READY] Update API documentation The website containing the API docs wasn't updated when merging PR #857 (the `/receive_messages` handler is missing). This PR fixes some typos, renames `openapi.yaml` to `openapi.yml`, and updates the website. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/ycmd/911) <!-- Reviewable:end -->
[READY] Update API documentation The website containing the API docs wasn't updated when merging PR #857 (the `/receive_messages` handler is missing). This PR fixes some typos, renames `openapi.yaml` to `openapi.yml`, and updates the website. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/ycmd/911) <!-- Reviewable:end -->
[READY] Update ycmd Include the following changes: - PR ycm-core/ycmd#789: add support for Windows flags when --driver-mode=cl is given; - PR ycm-core/ycmd#848: hide C++ symbols by default; - PR ycm-core/ycmd#857: add Java support using jdt.ls; - PR ycm-core/ycmd#861: translate libclang error codes to exceptions; - PR ycm-core/ycmd#880: support downloading Clang binaries on ARM systems; - PR ycm-core/ycmd#883: handle zero column diagnostic from OmniSharp; - PR ycm-core/ycmd#884: specify Platform property when compiling OmniSharp; - PR ycm-core/ycmd#886: use current working directory in JavaScript completer; - PR ycm-core/ycmd#887: update Boost to 1.66.0; - PR ycm-core/ycmd#888: update JediHTTP; - PR ycm-core/ycmd#889: update Clang to 5.0.1; - PR ycm-core/ycmd#891: fix building with system libclang on Gentoo amd64; - PR ycm-core/ycmd#904: drop Python 2.6 and Python 3.3 support; - PR ycm-core/ycmd#905: calculate the start column when items are not resolved in the language server completer; - PR ycm-core/ycmd#912: download Clang binaries from HTTPS; - PR ycm-core/ycmd#914: do not try to symlink libclang on Windows. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/2902) <!-- Reviewable:end -->
[READY] Java support with asynchronous diagnostics and messages # PR Prelude Thank you for working on YCM! :) **Please complete these steps and check these boxes (by putting an `x` inside the brackets) _before_ filing your PR:** - [x] I have read and understood YCM's [CONTRIBUTING][cont] document. - [x] I have read and understood YCM's [CODE_OF_CONDUCT][code] document. - [x] I have included tests for the changes in my PR. If not, I have included a rationale for why I haven't. - [x] **I understand my PR may be closed if it becomes obvious I didn't actually perform all of these steps.** # Why this change is necessary and useful This change is required for a better user experience when using native java support This implements an asynchronous message system using a long-poll request to the server. The server provides an endpoint /receive_messages which blocks until either a timeout occurs or we receive a batch of asynchronous messages. We send this request asynchronously and poll it 4 times a second to see if we have received any messages. The messages may either be simply for display (such as startup progress) or diagnostics, which override the diagnostics returned by OnFileReqdyToParse. In the former case, we simply display the message, accepting that this might be overwritten by any other message (indeed, requiring this), and for the latter we fan out diagnostics to any open buffer for the file in question. Unfortunately, Vim has bugs related to timers when there is something displayed (such as a "confirm" prompt or other), so we suspend background timers when doing subcommands to avoid vim bugs. NOTE: This requires a new version of Vim (detected by the presence of the particular functions used). NOT_READY because: - the submodule commit points at my repo and requires ycm-core/ycmd#857 to be merged - my spider sense suggest i have more testing to do... Notes: - Part 3 (I think) of the Java support PRs. This one actually adds the minimal changes for working java support - There are about 2 or 3 other PRs to come to add things like automatic module imports, etc. [Please explain **in detail** why the changes in this PR are needed.] [cont]: https://github.com/Valloric/YouCompleteMe/blob/master/CONTRIBUTING.md [code]: https://github.com/Valloric/YouCompleteMe/blob/master/CODE_OF_CONDUCT.md <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/2863) <!-- Reviewable:end -->
OK take a deep breath, this is a monster PR.
First and foremost, massive thanks to @bstaletic and @micbou for helping not
only with the development and testing, but also for moral support!
Overview
This PR implements native support in ycmd for the Java language, based on
jdt.ls. In summary, the following key features are supported:
build.py
)far as is required for jdt.ls (easily extensible to other engines)
See the trello board for a more complete picture.
Show me the demo, already.
OK sure.
Startup and completion
Rename
FixIt
GoToReferences
Why is this necessary and useful?
The current state of the plugin for YCM support for Java is basically eclim
or javacomplete2, neither of which work perfectly with ycmd, and neither of
which are quite as well integrated as we might like.
eclim
Eclim is actually pretty good and I have used it for a long time. However, the
experience isn't optimal for YCM:
Pros
Cons
successfully got it working), requiring a Vimball, installation into an
existing eclipse installation, manually starting the server, and other fiddly
stuff.
number of other times.
javacomplete2
I have not extensively used javacomplete2, but I know there have been issues
reported that its omnifunc doesn't always play nice with ycmd.
Pros?
Cons?
Notable mentions
tried it but my experience with jdt.ls is that there is no way to make a fully
working LSP client without server-specific knowledge. I don't know if this
works with jdt.ls
Why jdt.ls?
I tried 2 different Java LSP engines:
this is a nice and simple server and has decent features.
popular Java plugin for VSCode, and has the full power of RedHat and
Microsoft investing in bringing JDT (the eclipse Java engine) to VSCode and
the LSP-using community.
Initially, for its simplicity, I was drawn to vscode-javac
for its simplicity and ease of integration with ycmd. However, I also
implemented jdt.ls and found this, while more work, to be a superior
experience. It also has a very active development community, which bodes well
for the future.
Why such a big PR, man. Come on!
I'm sorry. I really really am. I honestly tried to rebase and rewrite the ~150
commits over the last 1 year or so, but as with many such things, a lot of the
functionality that would make "Phase 1" ended up relying on functionality and
test framework that came in "Phase N". So I'm dumping this on you in one
unwieldy heap and hoping that you can find it in your hearts to forgive me. I
honestly spent hours trying :(
OK, but come on, give us a hand...
Sure thing! I've tried to give below a "review guide" to help you get your heads
around what's going on an why some of the design decisions where made.
Overall design/goals
Key goals:
javacomplete2 for most people
suffer from yagni); prove that this works.
An overview of the objects involved can be seen on this
card. In short:
language_server_completer.py
module:LanguageServerConnection
- an abstraction of the comminication with theserver, which may be over stdio or any number of TCP/IP ports (or a domain
socket, etc.). Only a single implementation is included (stdio), but
implementations for TCP/IP
were written originally and dropped in favour of stdio's simplicity.
LanguageServerCompleter
- an abstract base for any completer based on LSP,which implements as much standard functionality as possible including
completions, diagnostics, goto, fixit, rename, etc.
java_completer
itself implements theLanguageServerCompleter
, bootsthe jdt.ls server, and instantiates a
LanguageServerConnection
forcommunication with jdt.ls.
The overall plan and some general discussion around the project can be found on
the trello board I used for development.
The proof? I implemented a fully working clangd completer for ycmd in about 30 minutes. The only wrinkles were clangd bugs!
Threads, why oh why so many threads.
OK chill. It's not thhaartd.
LSP is by its nature an asyncronous protocol. There are request-reply like
requests
and unsolicitednotifications
. Receipt of the latter is mandatory,so we cannot rely on there being a
bottle
thread executing a client request.So we need a message pump and despatch thread. This is actually the
LanguageServerConnection
, which implementsthread
. It's main method simplylistens on the socket/stream and despatches complete messages to the
LanguageServerCompleter
. It does this:requests
: similarly to the TypeScript completer, using pythonevent
objects, wrapped in our
Response
classnotifications
: via a synchronisedqueue
. More on this later.A very poor representation of this is on the "Requests and notifications" page
of the design, including a rough sketch of the thread interaction.
Some handling is done in the message pump.
That's right. There are certain notifications which we have to handle when we
get them, such as:
In these cases, we allow some code to be executed inline within the message pump
thread, as there is no other thread guaranteed to execute. These are handled by
callback functions and mutexes.
Startup sequence
See the 'initialisation sequence' tab on the design for a bit of background.
In standard LSP, the initialisation sequence consists of an initialise
request-reply, followed by us sending the server an initialised notification. We
must not send any other requests until this has completed.
An additional wrinkle is that jdt.ls, being based on eclipse has a whole other
initialisation sequence during which time it is not fully functional, so we have
to determine when that has completed too. This is done by jdt.ls-specific
messages and controls the
ServerIsReady
response.In order for none of these shenanigans to block the user, we must do them all
asynchronously, effectively in the message pump thread. In addition, we must
queue up any file contents changes during this period to ensure the server is up
to date when we start processing requests proper.
This is unfortunately complicated, but there were early issues with really bad
UI blocking that we just had to get rid of.
Completion foibles
Language server protocol requires that the client can apply textEdits,
rather than just simple text. This is not an optional feature, but ycmd
clients do not have this ability.
The protocol, however, restricts that the edit must include the original
requested completion position, so we can perform some simple text
manipulation to apply the edit to the current line and determine the
completion start column based on that.
In particular, the jdt.ls server returns textEdits that replace the
entered text for import completions, which is one of the most useful
completions.
We do this super inefficiently by attempting to normalise the TextEdits
into insertion_texts with the same start_codepoint. This is necessary
particularly due to the way that eclipse returns import completions for
packages.
We also include support for "additionalTextEdits" which
allow automatic insertion of, e.g., import statements when selecting
completion items. These are sent on the completion response as an
additional completer data item called 'fixits'. The client applies the
same logic as a standard FixIt once the selected completion item is
inserted.
Diagnostics foibles
Diagnostics in LSP are delivered asynchronously via
notifications
. Normally,we would use the
OnFileReadyToParse
response to supply diagnostics, but due tothe lag between refreshing files and receiving diagnostics, this leads to a
horrible user experience where the diagnostics always lag one edit behind.
To resolve this, we use the long-polling mechanism added here (
ReceiveMessages
request) to return diagnostics to the client asynchronously.
We deliver asynchronous diagnostics to the client in the same way that the
language server does, i.e. per-file. The client then fans them out or does
whatever makes sense for the client. This is necessary because it isn't possible
to know when we have received all diagnostics, and combining them into a single
message was becoming clunky and error prone.
In order to be relatively compatible with other clients, we also return
diagnostics on the file-ready-to-parse event, even though they might be
out of date wrt the code. The client is responsible for ignoring these
diagnostics when it handles the asynchronously delivered ones. This requires
that we hold the "latest" diagnostics for a file. As it turns out, this is also
required for FixIts.
Projects
jdt.ls is based on eclipse. It is in fact an eclipse plugin. So it requires an
eclipse workspace. We try and hide this by creating an ad-hoc workspace for each
ycmd instance. This prevents the possibility of multiple "eclipse" instances
using the same workspace, but can lead to unreasonable startup times for large
projects.
The jdt.ls team strongly suggest that we should re-use a workspace based on the
hash of the "project directory" (essentially the dir containing the project
file:
.project
,pom.xml
orbuild.gradle
). They also say, however, thateclipse frequently corrupts its workspace.
So we have a hidden switch to re-use a workspace as the jdt.ls devs suggest. In
testing at work, this was mandatory due to a slow SAN, but at home, startup
time is not an issue, even for large projects. I think we'll just have to see
how things go to decide which one we want to keep.
Subcommand foibles
GetDoc/GetType
There is no GetType in LSP. There's only "hover". The hover response is
hilariously server-specific, so in the base
LanguageServerCompleter
we justprovide the ability to get the
hover
response andJavaCompleter
extracts theappropriate info from there. Thanks to @bstaletic for this!
FixIt
FixIts are implemented as code actions, and require the diagnostic they relate
to to be send from us to the server, rather than just a position. We use the
stored diags and find the nearest one based on the
request_data
.What's worse is that the LSP provides no documentation for what the "Code
action" response should be, and it is 100% implementation-specific. They just
have this
command
abstraction which is basically "do a thing".From what I've seen, most servers just end up with either a
WorkspaceEdit
or aseries of
TextEdits
, which is fine for us as that's what ycmd's protocol lookslike.
The solution is that we have a callback into the
JavaCompleter
to handle the(custom)
java.apply.workspaceEdit
"command".GoToReferences
Annoyingly, jdt.ls sometimes returns references to .class files within jar
archives using a custom
jdt://
protocol. We can't handle that, so we have tododge and weave so that we don't crash.
Stopping the server
Much like the initialisation sequence, the LSP shutdown sequence is a bit
fiddly. 2 things are required:
shutdown
request-reply. The server tides up and prepares to die!exit
notification. We tell the server to die.This isn't so bad, but jdt.ls is buggy and actually dies without responding to
the
shutdown
request. So we have a bunch of code to handle that and to ensurethat the server dies eventually, as it had a habbit of getting stuck running,
particularly if we threw an exception.
Closing remarks
Phew. That was a lot of caveats! I have personally used this quite a lot now,
and it has proven to be really useful. In particular the full index and
GoToReferences, etc.
I think the tests are comprehensive, but there is probably more work to do on
coverage. Some of it will be tricky (particularly exceptions around
jdt:
andother edge cases in jdt.ls that I've come across).
Ship it as experimental ?
Due to the rapid pace of development of jdt.ls and the scale of this change, one
thought I had was to mark Java support in YCM as exprrimental and gather
feedback via an open github issue. I'm certain there will be issues, and
preferences, etc. so this might be a good way to tackle that.
Where's the client PR ?
I haven't finished polishing the client yet, but you can get use it by checking
out my
language-server-java
branch of YCM.This change is