Navigate authoritative FXML locations, withdraw stale results and preserve host Undo/Redo. Include editor ownership in native problem identity so equal diagnostics from separate owners survive independent cleanup. Qualify both hosts automatically.
151 lines
7.5 KiB
Java
151 lines
7.5 KiB
Java
package de.neitzel.fxdesigner.intellij;
|
|
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.application.WriteIntentReadAction;
|
|
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.Disposer;
|
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
|
import de.neitzel.fxdesigner.host.*;
|
|
import java.nio.file.Path;
|
|
import java.util.concurrent.*;
|
|
|
|
/**
|
|
* IDE composition; shared build execution exposes output/cancellation to IDE presentation
|
|
* callbacks.
|
|
*/
|
|
public final class IntellijHost implements AutoCloseable {
|
|
private final SessionScope scope = new SessionScope();
|
|
private final HostServices services;
|
|
|
|
public IntellijHost(
|
|
Project project, HostServices.Runtimes runtimes, HostServices.Diagnostics diagnostics) {
|
|
var docs = scope.own(new IntellijDocuments(project));
|
|
var builds = new ProcessBuilds();
|
|
var problems = scope.own(new IntellijDiagnostics(project, diagnostics));
|
|
services =
|
|
new HostServices(
|
|
docs,
|
|
(request, output) -> scope.own(builds.start(request, output)),
|
|
runtimes,
|
|
new HostServices.Navigation() {
|
|
public CompletionStage<java.util.List<HostServices.Location>> componentSources(
|
|
String type) {
|
|
if (!type.matches("[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*"))
|
|
return CompletableFuture.failedFuture(
|
|
new IllegalArgumentException("Invalid type"));
|
|
return CompletableFuture.supplyAsync(
|
|
() ->
|
|
com.intellij.openapi.application.ReadAction.compute(
|
|
() -> {
|
|
var result = new java.util.ArrayList<HostServices.Location>();
|
|
String path = type.replace('.', '/');
|
|
for (var owner :
|
|
com.intellij.openapi.project.ProjectManager.getInstance()
|
|
.getOpenProjects()) {
|
|
var roots =
|
|
com.intellij.openapi.roots.ProjectRootManager.getInstance(owner)
|
|
.getContentSourceRoots();
|
|
boolean declared =
|
|
java.util.Arrays.stream(roots)
|
|
.anyMatch(
|
|
r -> r.findFileByRelativePath(path + ".java") != null);
|
|
if (declared)
|
|
for (var root : roots) {
|
|
var file = root.findFileByRelativePath(path + ".fxml");
|
|
if (file != null && file.isInLocalFileSystem())
|
|
result.add(
|
|
new HostServices.Location(
|
|
Path.of(file.getPath()).toUri(), 0));
|
|
}
|
|
}
|
|
return java.util.List.copyOf(result);
|
|
}));
|
|
}
|
|
|
|
public CompletionStage<Void> openSource(HostServices.Location location) {
|
|
var result = new CompletableFuture<Void>();
|
|
ApplicationManager.getApplication()
|
|
.invokeLater(
|
|
() ->
|
|
WriteIntentReadAction.run(
|
|
() -> {
|
|
try {
|
|
if (project.isDisposed())
|
|
throw new IllegalStateException("Project disposed");
|
|
var file =
|
|
LocalFileSystem.getInstance()
|
|
.refreshAndFindFileByNioFile(
|
|
Path.of(location.document()));
|
|
if (file == null)
|
|
throw new IllegalArgumentException(
|
|
"Navigation file unavailable");
|
|
new OpenFileDescriptor(project, file, location.offset())
|
|
.navigate(true);
|
|
result.complete(null);
|
|
} catch (Throwable e) {
|
|
result.completeExceptionally(e);
|
|
}
|
|
}));
|
|
return result;
|
|
}
|
|
|
|
public CompletionStage<Void> open(HostServices.Location location) {
|
|
var result = new CompletableFuture<Void>();
|
|
ApplicationManager.getApplication()
|
|
.invokeLater(
|
|
() ->
|
|
WriteIntentReadAction.run(
|
|
() -> {
|
|
try {
|
|
if (project.isDisposed())
|
|
throw new IllegalStateException("Project disposed");
|
|
var file =
|
|
LocalFileSystem.getInstance()
|
|
.refreshAndFindFileByNioFile(
|
|
Path.of(location.document()));
|
|
if (file == null)
|
|
throw new IllegalArgumentException(
|
|
"Navigation file unavailable");
|
|
Project owner = project;
|
|
for (var candidate :
|
|
com.intellij.openapi.project.ProjectManager.getInstance()
|
|
.getOpenProjects())
|
|
if (!candidate.isDisposed()
|
|
&& com.intellij.openapi.roots.ProjectFileIndex
|
|
.getInstance(candidate)
|
|
.isInSourceContent(file)) {
|
|
owner = candidate;
|
|
break;
|
|
}
|
|
if ("fxml".equalsIgnoreCase(file.getExtension())) {
|
|
var manager =
|
|
com.intellij.openapi.fileEditor.FileEditorManager
|
|
.getInstance(owner);
|
|
manager.openFile(file, true);
|
|
manager.setSelectedEditor(file, "neitzel-fx-designer");
|
|
} else
|
|
new OpenFileDescriptor(owner, file, location.offset())
|
|
.navigate(true);
|
|
result.complete(null);
|
|
} catch (Throwable e) {
|
|
result.completeExceptionally(e);
|
|
}
|
|
}));
|
|
return result;
|
|
}
|
|
},
|
|
problems,
|
|
scope);
|
|
Disposer.register(project, this::close);
|
|
}
|
|
|
|
public HostServices services() {
|
|
return services;
|
|
}
|
|
|
|
public void close() {
|
|
scope.close();
|
|
}
|
|
}
|