Skip to content
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

Setup JDT LS extension to provide Java + Kotlin interoperability #334

Merged
merged 9 commits into from
Apr 23, 2022

Conversation

daplf
Copy link
Contributor

@daplf daplf commented Mar 6, 2022

Fixes #192

Sets up a VSCode extension to allow Java code to see Kotlin code. The extension depends on both vscode-java and vscode-kotlin. It uses the JDT LS extension mechanism, through a custom JDT LS extension that provides kotlin project importers for maven and gradle.

This seems to work reasonably well for small projects at least. I don't know how well it performs on bigger projects, but I think the concept looks valid and we can always improve that later.

Some notes:

  • The VSCode extension uses the javaExtensions contribution point to provide vscode-java with the JAR for the JDT LS extension. The JDT LS uses the jar to find the kotlin project importers. The importers have higher priority than the default ones provided by the JDT LS itself.
  • The JDT LS extension was built with Maven and Java since it would have been much harder to do it with Gradle and Kotlin (due to lack of documentation and editor support).
  • The importers actually extend MavenProjectImporter and GradleProjectImporter provided by the JDT LS. As such, pretty much all the importing logic is done by the JDT LS. Once the core java project is imported (by MavenProjectImporter or GradleProjectImporter) we add the compiled kotlin files to the project's classpath. This is how the java code is then able to see the kotlin code.
  • At the moment, the kotlin language server does not generate any bytecode (at least as far as I could tell). This PR also introduces this, since it is required for the JDT LS extension to work. The bytecode generated by the kotlin language server is written to a folder called kls at the root of the workspace. I chose this because the JDT LS messes with the default maven and gradle output paths (and we should try to isolate the language servers as much as possible). The code generation is only triggered on file save for performance reasons.
  • Some language features are not supported yet (e.g., go to definition from java to kotlin doesn't work as you would expect it to), but the projects should no longer complain about missing symbols when referencing kotlin classes, etc.

How to test this for now:

On the vscode-java-kotlin folder, run the build.sh script to build the JDT LS extension. Afterwards, run npm install to install the VSCode extension dependencies. Once this is done, you can either debug it (using F5 on VSCode), or package it and install it from a VSIX.
Important: The JDT LS extension will only work with the latest kotlin language server code (i.e, the one in this PR), so you should install the language server locally and setup your vscode-kotlin to use that (through the kotlin.languageServer.pathconfiguration property)

This is very experimental (I cannot stress this enough). This is literally the first thing I wrote involving Eclipse PDE and the JDT LS API. With that said, I think the concept at least seems to work, so I'm guessing we can improve this more over time.

Feedback welcome :)

@daplf daplf changed the title Setup JDT LS extension to provide Kotlin + Java interoperability Setup JDT LS extension to provide Java + Kotlin interoperability Mar 6, 2022
@daplf daplf marked this pull request as ready for review March 6, 2022 17:13
@fwcd fwcd added enhancement New feature or request java interop Interoperability with Java/JDT.LS maven Related to the language server's support for Maven projects labels Mar 7, 2022
@fwcd
Copy link
Owner

fwcd commented Mar 7, 2022

Very nice, thanks a lot for tackling this issue and the writeup, looks pretty good at first glance! I hope to find some time to review this in detail soon.

Similar to vscode-kotlin, I would move vscode-java-kotlin to its own repo, wdyt? (though we could leave it here at least until the PR is merged for easier reviewability)

@daplf
Copy link
Contributor Author

daplf commented Mar 7, 2022

@fwcd Sure, I'm fine either way. If you want, you can create the new repo and I can make a separate PR there with the vscode + JDT part. And we can keep this PR for the changes to the KLS only. Whatever you prefer.

@daplf
Copy link
Contributor Author

daplf commented Mar 11, 2022

@fwcd FYI, I found some more time and added gradle support as well.

fwcd pushed a commit to daplf/vscode-java-kotlin that referenced this pull request Mar 17, 2022
@fwcd
Copy link
Owner

fwcd commented Mar 17, 2022

I have moved the vscode-java-kotlin extension to its own repository (under MIT license like vscode-kotlin), to which I've given you write access, so feel free to tinker around on the main branch for now (though keeping the PR workflow would still be great in the future). I hope to find some time to take a closer look at this PR and the extension soon.

@@ -95,6 +98,10 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
workspaceRoots.add(root)
javaSourcePath.addAll(findJavaSourceFiles(root))

val outputDir = Paths.get(root.toString(), "kls").toFile()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefix that directory with a dot, e.g. .kls, also perhaps a less generic name? Alternatively, could we perhaps use the storagePath introduced in #337, so we don't have to put custom build outputs into the user's project?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your second suggestion would be perfect, but unfortunately it'll be tricky to implement (although doable).

The problem is that the JDT LS extension needs to know where to find the build outputs. A directory inside the user's project is an easy solution, because the JDT LS extension can know about this without any help. If we use a different directory, we will need to establish communication between the JDT LS extension and the kotlin language server. This is obviously doable and ideal, but it is trickier to implement.

I would suggest using .kls for now (your first suggestion) and in a later PR we could implement the other approach. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey. So I thought a little more about this and I think I might have an alternative approach to this that would make things a lot cleaner.

Instead of using custom project importers on the JDT LS extension, we could have a delegateCommandHandler that adds the build output location to the java project classpath. With this approach, the java project would be imported using the normal JDT project importers but we would change the classpath as soon as the KLS is setup. In order for this to work, the KLS would need to be able to call the delegateCommandHandler once it knows where it will store the build outputs. This call would probably need to go through the vscode extensions, since a direct communication between the language servers is tricky. The cool thing about this approach is that the JDT LS would just work normally at first and as soon as the KLS starts, it would be notified to update its project. This approach would also allow us to use temp directories as a default build output location, which is a lot cleaner (especially for people who are not even working on a Java + Kotlin project)

@@ -263,6 +266,7 @@ class KotlinTextDocumentService(
fun lintAll() {
debounceLint.submitImmediately {
sp.compileAllFiles()
sp.saveAllFiles()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does separately emitting compilation outputs have a non-negligible performance impact?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the tests I've done were fine, but they were not exhaustive. With that said, this particular part of the code is done async, so I don't think it should be too problematic. I suggest you check it yourself just in case.

Besides this, the only other time we emit code is on file save (which doesn't happen that often, and only for one file at once).

@daplf
Copy link
Contributor Author

daplf commented Mar 23, 2022

@fwcd I updated the PR to use the approach I described in a previous comment (i.e., use a delegateCommandHandler). It requires changes on the vscode and JDT side as well:

fwcd/vscode-kotlin#89
https://github.com/fwcd/vscode-java-kotlin/pull/2

This approach is now a lot cleaner, since it uses temp directories for the build outputs (thus no longer polluting the user workspace)

@daplf
Copy link
Contributor Author

daplf commented Apr 15, 2022

Hey @fwcd
Do you think you'll have the time to take a look at this and the other PRs soon? This seems to be a fairly requested feature so it would be cool if we could ship it soon.

Copy link
Owner

@fwcd fwcd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, looks mostly good, just some minor things.

@daplf daplf requested a review from fwcd April 22, 2022 20:47
@fwcd
Copy link
Owner

fwcd commented Apr 23, 2022

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request java interop Interoperability with Java/JDT.LS maven Related to the language server's support for Maven projects
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement JDT.LS extension for Kotlin files
2 participants