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> 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(); 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 openSource(HostServices.Location location) { var result = new CompletableFuture(); 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 open(HostServices.Location location) { var result = new CompletableFuture(); 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(); } }