Skip to content

Commit 6c7856d

Browse files
committed
Setup delegateCommandHandler to set KLS classpath entry
1 parent 8efa0c9 commit 6c7856d

File tree

8 files changed

+107
-158
lines changed

8 files changed

+107
-158
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ target
66
.DS_Store
77
**/.DS_Store
88
jars
9-
.vscode/settings.json
9+
.vscode/settings.json
10+
out

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# VSCode Extension for Java + Kotlin
22

3-
A VSCode extension that enchances [vscode-java](https://github.com/redhat-developer/vscode-java) and [vscode-kotlin](https://github.com/fwcd/vscode-kotlin) with java + kotlin interoperability. This uses a JDT LS extension with a custom project importer to allow Java code to have access to Kotlin code.
3+
A VSCode extension that enchances [vscode-java](https://github.com/redhat-developer/vscode-java) and [vscode-kotlin](https://github.com/fwcd/vscode-kotlin) with java + kotlin interoperability. This uses a JDT LS extension with a delegate command handler that can work together with the Kotlin Language Server, in order for Java code to have access to Kotlin code.
44

55
**Disclaimer**: This is very experimental, but it seems to work for small maven and gradle projects at least.
66

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<?eclipse version="3.4"?>
33
<plugin>
4-
<extension
5-
id="importers"
6-
point="org.eclipse.jdt.ls.core.importers">
7-
<importer
8-
id = "kotlinGradleImporter"
9-
order = "125"
10-
class = "org.javacs.kt.jdt.ls.extension.KotlinGradleImporter"/>
11-
<importer
12-
id = "kotlinMavenImporter"
13-
order = "150"
14-
class = "org.javacs.kt.jdt.ls.extension.KotlinMavenImporter"/>
4+
<extension point="org.eclipse.jdt.ls.core.delegateCommandHandler">
5+
<delegateCommandHandler class="org.javacs.kt.jdt.ls.extension.SetKotlinBuildOutputCommandHandler">
6+
<command
7+
id="kotlin.java.setKotlinBuildOutput"
8+
static="true"/>
9+
</delegateCommandHandler>
1510
</extension>
1611
</plugin>
1712

jdt-ls-extension/org.javacs.kt.jdt.ls.extension/src/org/javacs/kt/jdt/ls/extension/KotlinGradleImporter.java

Lines changed: 0 additions & 65 deletions
This file was deleted.

jdt-ls-extension/org.javacs.kt.jdt.ls.extension/src/org/javacs/kt/jdt/ls/extension/KotlinImporterUtils.java

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,51 @@
2626
import org.eclipse.core.runtime.IStatus;
2727
import org.eclipse.core.runtime.Path;
2828
import org.eclipse.core.runtime.jobs.Job;
29+
import org.eclipse.jdt.core.IClasspathAttribute;
2930
import org.eclipse.jdt.core.IClasspathEntry;
3031
import org.eclipse.jdt.core.IJavaProject;
3132
import org.eclipse.jdt.core.JavaCore;
33+
import org.eclipse.jdt.internal.core.ClasspathEntry;
3234
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
3335
import org.eclipse.jdt.ls.core.internal.managers.IBuildSupport;
3436
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;
3537

3638
@SuppressWarnings("restriction")
3739
public class KotlinImporterUtils {
38-
39-
/**
40-
* Checks if any kotlin files exist in the given directory and subdirectories.
41-
*/
42-
public static boolean anyKotlinFiles(java.nio.file.Path directory) {
43-
try (Stream<java.nio.file.Path> files = Files.walk(directory)) {
44-
return files.anyMatch(f -> f.getFileName().toString().endsWith(".kt"));
45-
} catch (IOException ex) {
46-
return false;
47-
}
48-
}
4940

41+
/**
42+
* Sets the KLS classpath entry.
43+
* This is the path where the KLS stores build outputs (i.e., Kotlin compiled code).
44+
* The entry has an extra attribute, which is used to remove old paths whenever they change, since this is something that is kept between sessions.
45+
*/
5046
public static void setKlsClasspathEntry(IProject project, java.nio.file.Path path, IProgressMonitor monitor, IBuildSupport buildSupport) throws CoreException {
5147
IJavaProject javaProject = JavaCore.create(project);
5248

5349
List<IClasspathEntry> entries = new ArrayList<>(Arrays.asList(javaProject.getRawClasspath()));
5450
IPath klsPath = Path.fromOSString(path.toString());
5551

5652
entries.removeIf(entry -> entry.getPath().equals(klsPath));
57-
IClasspathEntry classpathEntry = JavaCore.newLibraryEntry(klsPath, null, null);
53+
entries.removeIf(entry -> Stream.of(entry.getExtraAttributes()).anyMatch(attribute -> attribute.getName().equals("kls")));
54+
IClasspathEntry classpathEntry = JavaCore.newLibraryEntry(
55+
klsPath,
56+
null,
57+
null,
58+
ClasspathEntry.NO_ACCESS_RULES,
59+
new IClasspathAttribute[]{
60+
JavaCore.newClasspathAttribute("kls", "true")
61+
},
62+
false
63+
);
5864
entries.add(classpathEntry);
5965

6066
javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[0]), monitor);
6167
buildSupport.refresh(project, CHANGE_TYPE.CHANGED, monitor);
6268
}
6369

6470
/**
65-
* Registers a watcher for the kls directory.
71+
* Registers a watcher for the kls build output directory.
6672
* Any file change leads to an update in the classpath.
67-
* The classpath update is done using a function send as parameter, since it's different for each importer.
73+
* The classpath update is done using a function sent as parameter, since it's dependent on the build tool used.
6874
*/
6975
public static void registerKlsWatcher(java.nio.file.Path path, Function<IProgressMonitor, IStatus> updateClasspathJob) {
7076
new Thread(() -> {

jdt-ls-extension/org.javacs.kt.jdt.ls.extension/src/org/javacs/kt/jdt/ls/extension/KotlinMavenImporter.java

Lines changed: 0 additions & 66 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.javacs.kt.jdt.ls.extension;
2+
3+
import java.nio.file.Path;
4+
import java.nio.file.Paths;
5+
import java.util.List;
6+
7+
import org.eclipse.core.resources.IProject;
8+
import org.eclipse.core.runtime.CoreException;
9+
import org.eclipse.core.runtime.IProgressMonitor;
10+
import org.eclipse.core.runtime.Status;
11+
import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler;
12+
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
13+
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
14+
import org.eclipse.jdt.ls.core.internal.managers.GradleBuildSupport;
15+
import org.eclipse.jdt.ls.core.internal.managers.IBuildSupport;
16+
import org.eclipse.jdt.ls.core.internal.managers.MavenBuildSupport;
17+
18+
/**
19+
* Delegate command handler that adds a Kotlin build output to the classpath.
20+
* This also registers a directory watcher to update the classpath as new compiled files are added and removed.
21+
*/
22+
@SuppressWarnings("restriction")
23+
public class SetKotlinBuildOutputCommandHandler implements IDelegateCommandHandler {
24+
25+
/**
26+
* Goes through all the projects and updates their classpath with the argument received in this command.
27+
*/
28+
@Override
29+
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor monitor) throws Exception {
30+
if (!arguments.isEmpty()) {
31+
String outputLocation = arguments.get(0).toString();
32+
Path path = Paths.get(outputLocation);
33+
34+
for (IProject project : ProjectUtils.getAllProjects()) {
35+
IBuildSupport buildSupport = null;
36+
37+
if (ProjectUtils.isMavenProject(project)) {
38+
buildSupport = new MavenBuildSupport();
39+
} else if (ProjectUtils.isGradleProject(project)) {
40+
buildSupport = new GradleBuildSupport();
41+
}
42+
43+
if (buildSupport != null) {
44+
KotlinImporterUtils.setKlsClasspathEntry(project, path, monitor, buildSupport);
45+
KotlinImporterUtils.registerKlsWatcher(path, subMonitor -> {
46+
try {
47+
for (IProject underlyingProject : ProjectUtils.getAllProjects()) {
48+
if (ProjectUtils.isMavenProject(project)) {
49+
KotlinImporterUtils.setKlsClasspathEntry(underlyingProject, path, subMonitor, new MavenBuildSupport());
50+
} else if (ProjectUtils.isGradleProject(project)) {
51+
KotlinImporterUtils.setKlsClasspathEntry(underlyingProject, path, subMonitor, new GradleBuildSupport());
52+
}
53+
}
54+
55+
return Status.OK_STATUS;
56+
} catch (CoreException ex) {
57+
JavaLanguageServerPlugin.logException(ex);
58+
return Status.error("An error occurred while updating the kotlin classpath entry");
59+
}
60+
});
61+
}
62+
}
63+
}
64+
65+
return null;
66+
}
67+
}

src/extension.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,15 @@ import * as vscode from 'vscode';
44

55
/** Called when extension is activated */
66
export async function activate(context: vscode.ExtensionContext) {
7+
// We call vscode-kotlin's API to fetch the build output path.
8+
// Note that this code is only executed after vscode-kotlin is setup, so the API should be ready to receive requests.
9+
const vscodekotlinApi = vscode.extensions.getExtension('fwcd.kotlin').exports as ExtensionAPI;
10+
11+
// Execute a java workspace command to update the kotlin build output path. This triggers a classpath change on the JDT language server.
12+
await vscode.commands.executeCommand("java.execute.workspaceCommand", "kotlin.java.setKotlinBuildOutput", vscodekotlinApi.getBuildOutputPath());
713
}
14+
15+
// vscode-kotlin API stub.
16+
interface ExtensionAPI {
17+
getBuildOutputPath(): string;
18+
}

0 commit comments

Comments
 (0)