Preserve FXML source ranges and extend shared Component projection
This commit is contained in:
@@ -28,6 +28,14 @@
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Keep the main UI before its test-support dependency so its patched Kit class wins. -->
|
||||
<dependency>
|
||||
<groupId>de.neitzel.fxdesigner</groupId>
|
||||
<artifactId>designer-kit-ui</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
+25
-2
@@ -64,7 +64,11 @@ public final class FileDocuments implements HostServices.Documents, AutoCloseabl
|
||||
}
|
||||
|
||||
public CompletionStage<List<DocumentSnapshot>> snapshots() {
|
||||
return submit(() -> documents.values().stream().map(FileDocuments::snapshot).toList());
|
||||
return submit(
|
||||
() -> {
|
||||
for (State s : documents.values()) refresh(s);
|
||||
return documents.values().stream().map(FileDocuments::snapshot).toList();
|
||||
});
|
||||
}
|
||||
|
||||
public CompletionStage<List<DocumentSnapshot>> unsaved() {
|
||||
@@ -76,6 +80,18 @@ public final class FileDocuments implements HostServices.Documents, AutoCloseabl
|
||||
.toList());
|
||||
}
|
||||
|
||||
private void refresh(State s) throws java.io.IOException {
|
||||
if (!s.text.equals(s.saved)) return; // Dirty draft belongs to the user; save detects conflict.
|
||||
String disk = Files.readString(s.file);
|
||||
if (!disk.equals(s.saved)) {
|
||||
s.text = s.saved = disk;
|
||||
s.undo.clear();
|
||||
s.redo.clear();
|
||||
s.revision++;
|
||||
changes.forEach(listener -> listener.accept(s.file.toUri()));
|
||||
}
|
||||
}
|
||||
|
||||
private static DocumentSnapshot snapshot(State s) {
|
||||
return new DocumentSnapshot(
|
||||
s.file.toUri(), s.revision, s.text, Files.isWritable(s.file), !s.text.equals(s.saved));
|
||||
@@ -98,12 +114,17 @@ public final class FileDocuments implements HostServices.Documents, AutoCloseabl
|
||||
}
|
||||
|
||||
public CompletionStage<DocumentSnapshot> snapshot() {
|
||||
return run(() -> FileDocuments.snapshot(s));
|
||||
return run(
|
||||
() -> {
|
||||
refresh(s);
|
||||
return FileDocuments.snapshot(s);
|
||||
});
|
||||
}
|
||||
|
||||
public CompletionStage<DocumentSnapshot> apply(EditBatch edits) {
|
||||
return run(
|
||||
() -> {
|
||||
refresh(s);
|
||||
String updated = edits.applyTo(FileDocuments.snapshot(s));
|
||||
if (!updated.equals(s.text)) {
|
||||
s.undo.push(s.text);
|
||||
@@ -154,6 +175,8 @@ public final class FileDocuments implements HostServices.Documents, AutoCloseabl
|
||||
!= null) {
|
||||
Files.setPosixFilePermissions(temp, Files.getPosixFilePermissions(s.file));
|
||||
}
|
||||
if (!Files.readString(s.file).equals(s.saved))
|
||||
throw new ConcurrentModificationException("External change; draft retained");
|
||||
Files.move(
|
||||
temp,
|
||||
s.file,
|
||||
|
||||
+28
@@ -67,4 +67,32 @@ class FileDocumentsTest {
|
||||
assertEquals("draft", d.snapshot().toCompletableFuture().get().text());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cleanExternalChangesReloadAndInvalidateOldTransactions() throws Exception {
|
||||
Path file = folder.resolve("External.fxml");
|
||||
Files.writeString(file, "old");
|
||||
try (var docs = new FileDocuments()) {
|
||||
var first = docs.open(file.toUri()).toCompletableFuture().get();
|
||||
var second = docs.open(file.toUri()).toCompletableFuture().get();
|
||||
var initial = first.snapshot().toCompletableFuture().get();
|
||||
Files.writeString(file, "external");
|
||||
var reloaded = second.snapshot().toCompletableFuture().get();
|
||||
assertEquals("external", reloaded.text());
|
||||
assertFalse(reloaded.dirty());
|
||||
assertNotEquals(initial.revision(), reloaded.revision());
|
||||
assertThrows(
|
||||
ExecutionException.class,
|
||||
() ->
|
||||
first
|
||||
.apply(
|
||||
new EditBatch(
|
||||
initial.revision(),
|
||||
"old edit",
|
||||
List.of(new EditBatch.Replacement(0, 1, "X"))))
|
||||
.toCompletableFuture()
|
||||
.get());
|
||||
assertEquals("external", first.undo().toCompletableFuture().get().text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-3
@@ -127,15 +127,30 @@ public final class KitWorkflowSmoke {
|
||||
var field =
|
||||
(TextField)
|
||||
pane.lookupAll(".text-field").stream()
|
||||
.filter(n -> n instanceof TextField t && t.getText().equals(caption))
|
||||
.filter(
|
||||
n ->
|
||||
n instanceof TextField t
|
||||
&& t.getText().equals(caption)
|
||||
&& (t.getId() == null
|
||||
|| !t.getId().startsWith("component-property-")))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
field.setText("alpha bravo charlie");
|
||||
pane.applyCss();
|
||||
pane.layout();
|
||||
var result = new StringBuilder();
|
||||
TextSelectionChecks.run(field, result);
|
||||
System.out.print(result);
|
||||
try {
|
||||
TextSelectionChecks.run(field, result);
|
||||
} finally {
|
||||
System.out.print(result);
|
||||
System.out.println(
|
||||
"Caption field: "
|
||||
+ field.getId()
|
||||
+ " width="
|
||||
+ field.getWidth()
|
||||
+ " text="
|
||||
+ field.getText());
|
||||
}
|
||||
field.setText(caption);
|
||||
field.fireEvent(new javafx.event.ActionEvent());
|
||||
return null;
|
||||
@@ -223,6 +238,10 @@ public final class KitWorkflowSmoke {
|
||||
if (!Files.readString(Path.of(args[1])).contains("Native canvas edit"))
|
||||
throw new AssertionError("Host save missing");
|
||||
}
|
||||
try (var document =
|
||||
host.services().documents().open(Path.of(args[1]).toUri()).toCompletableFuture().get()) {
|
||||
ProjectionHostChecks.run(pane, document);
|
||||
}
|
||||
fx(
|
||||
() -> {
|
||||
pane.close();
|
||||
|
||||
Reference in New Issue
Block a user