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.
This commit is contained in:
2026-11-13 16:52:50 +01:00
parent 910a8b0e85
commit 8a4f456e38
17 changed files with 883 additions and 17 deletions
+150
View File
@@ -82,6 +82,155 @@ public final class WorkflowProbe implements StartupActivity.DumbAware {
});
}
private static void checkProblems(
Project project,
VirtualFile file,
DesignerPane pane,
de.neitzel.fxdesigner.host.DocumentSession document)
throws Exception {
var original = document.snapshot().toCompletableFuture().get();
String invalid = original.text().replace("path=\"home\"", "path=\"problemMissing\"");
if (invalid.equals(original.text())) throw new AssertionError("Missing fixture binding");
document
.apply(
new de.neitzel.fxdesigner.contracts.EditBatch(
original.revision(),
"Problem fixture",
java.util.List.of(
new de.neitzel.fxdesigner.contracts.EditBatch.Replacement(
0, original.text().length(), invalid))))
.toCompletableFuture()
.get();
await(
() ->
fx(
() ->
pane.lookupAll(".text-area").stream()
.anyMatch(
n ->
n instanceof javafx.scene.control.TextArea t
&& t.getText().equals(invalid))));
fx(
() -> {
pane.lookupAll(".titled-pane").stream()
.filter(
n ->
n instanceof javafx.scene.control.TitledPane t
&& "Bindings".equals(t.getText()))
.forEach(n -> ((javafx.scene.control.TitledPane) n).setExpanded(true));
pane.lookupAll(".tab-pane").stream()
.filter(n -> n instanceof javafx.scene.control.TabPane)
.map(n -> (javafx.scene.control.TabPane) n)
.forEach(
t ->
t.getTabs().stream()
.filter(tab -> "Problems".equals(tab.getText()))
.findFirst()
.ifPresent(tab -> t.getSelectionModel().select(tab)));
return null;
});
click(pane, "Validate View");
var collector = com.intellij.analysis.problemsView.ProblemsCollector.getInstance(project);
await(
() ->
edt(
() ->
collector.getFileProblems(file).stream()
.anyMatch(p -> p.getText().contains("problemMissing"))));
var problem =
edt(
() ->
(com.intellij.analysis.problemsView.FileProblem)
collector.getFileProblems(file).stream()
.filter(p -> p.getText().contains("problemMissing"))
.findFirst()
.orElseThrow());
int expectedOffset =
edt(
() -> {
var d = FileDocumentManager.getInstance().getDocument(file);
return d.getLineStartOffset(problem.getLine()) + problem.getColumn();
});
String declaration =
invalid.substring(expectedOffset, invalid.indexOf('>', expectedOffset) + 1);
if (!declaration.startsWith("<DirectBinding")
|| !declaration.contains("path=\"problemMissing\""))
throw new AssertionError(
"Problems origin did not locate an affected binding: " + declaration);
edt(
() -> {
var window =
com.intellij.openapi.wm.ToolWindowManager.getInstance(project)
.getToolWindow("Problems View");
if (window == null) throw new AssertionError("Native Problems tool window missing");
window.show();
new OpenFileDescriptor(project, problem.getFile(), problem.getLine(), problem.getColumn())
.navigate(true);
return null;
});
await(
() ->
edt(
() -> {
var textEditor = FileEditorManager.getInstance(project).getSelectedTextEditor();
return textEditor != null
&& textEditor.getCaretModel().getOffset() == expectedOffset;
}));
if (!document.snapshot().toCompletableFuture().get().text().equals(invalid))
throw new AssertionError("Problems navigation mutated source");
document.undo().toCompletableFuture().get();
await(
() ->
edt(
() ->
collector.getFileProblems(file).stream()
.noneMatch(p -> p.getText().contains("problemMissing"))));
if (!document.snapshot().toCompletableFuture().get().text().equals(original.text()))
throw new AssertionError("Problems navigation changed undo history");
// Observe the shared editor's Undo revision before issuing Redo; its previous invalid
// text must not satisfy the Redo wait while the Undo notification is still queued.
await(
() ->
fx(
() ->
pane.lookupAll(".text-area").stream()
.anyMatch(
n ->
n instanceof javafx.scene.control.TextArea t
&& t.getText().equals(original.text()))));
document.redo().toCompletableFuture().get();
await(
() ->
fx(
() ->
pane.lookupAll(".text-area").stream()
.anyMatch(
n ->
n instanceof javafx.scene.control.TextArea t
&& t.getText().equals(invalid))));
click(pane, "Validate View");
await(
() ->
edt(
() ->
collector.getFileProblems(file).stream()
.anyMatch(p -> p.getText().contains("problemMissing"))));
edt(
() -> {
FileEditorManager.getInstance(project).closeFile(file);
return null;
});
await(
() ->
edt(
() ->
collector.getFileProblems(file).stream()
.noneMatch(p -> "Neitzel FX Designer".equals(p.getGroup()))));
System.out.println(
"PASS native Problems: current validation, FXML origin/navigation, correction via Undo,"
+ " redo validation and editor-close cleanup");
}
public void runActivity(Project project) {
if (!started.compareAndSet(false, true)) return;
ApplicationManager.getApplication()
@@ -238,6 +387,7 @@ public final class WorkflowProbe implements StartupActivity.DumbAware {
try (var document =
pane.kit().host().documents().open(path.toUri()).toCompletableFuture().get()) {
de.neitzel.fxdesigner.kit.ProjectionHostChecks.run(pane, document);
checkProblems(project, file, pane, document);
}
String version = fx(() -> System.getProperty("javafx.runtime.version"));
if (!version.startsWith("26"))