Files
NeitzelFXDesigner/designer-intellij/src/main/java/de/neitzel/fxdesigner/intellij/IntellijHost.java
T
konrad 8a4f456e38 Publish shared FXML diagnostics and integrate IntelliJ Problems
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.
2026-11-13 16:52:50 +01:00

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();
}
}