implements PLN-0016
This commit is contained in:
parent
928f844658
commit
c678fe6f49
@ -10,4 +10,4 @@
|
||||
{"type":"discussion","id":"DSC-0009","status":"open","ticket":"studio-debugger-workspace-integration","title":"Integrate ../debugger into Studio as a dedicated workspace","created_at":"2026-03-30","updated_at":"2026-03-30","tags":["studio","debugger","workspace","integration","shell"],"agendas":[{"id":"AGD-0009","file":"AGD-0009-studio-debugger-workspace-integration.md","status":"open","created_at":"2026-03-30","updated_at":"2026-03-30"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"studio-code-editor-workspace-foundations","title":"Establish Code Editor workspace foundations in Studio without LSP","created_at":"2026-03-30","updated_at":"2026-03-31","tags":["studio","editor","workspace","multi-frontend","lsp-deferred"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0010-studio-code-editor-workspace-foundations/LSN-0026-read-only-editor-foundations-and-semantic-deferral.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31"}]}
|
||||
{"type":"discussion","id":"DSC-0011","status":"done","ticket":"compiler-analyze-compile-build-pipeline-split","title":"Split compiler pipeline into analyze, compile, and build entrypoints","created_at":"2026-03-30","updated_at":"2026-03-30","tags":["compiler","pipeline","artifacts","build","analysis"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"discussion/lessons/DSC-0011-compiler-analyze-compile-build-pipeline-split/LSN-0025-compiler-pipeline-entrypoints-and-result-boundaries.md","status":"done","created_at":"2026-03-30","updated_at":"2026-03-30"}]}
|
||||
{"type":"discussion","id":"DSC-0012","status":"open","ticket":"studio-editor-document-vfs-boundary","title":"Definir um boundary de VFS documental para tree/view/open files no Code Editor do Studio","created_at":"2026-03-31","updated_at":"2026-03-31","tags":["studio","editor","workspace","vfs","filesystem","boundary"],"agendas":[{"id":"AGD-0012","file":"AGD-0012-studio-editor-document-vfs-boundary.md","status":"in_progress","created_at":"2026-03-31","updated_at":"2026-03-31"}],"decisions":[{"id":"DEC-0009","file":"DEC-0009-studio-prometeu-vfs-project-document-boundary.md","status":"in_progress","created_at":"2026-03-31","updated_at":"2026-03-31","ref_agenda":"AGD-0012"}],"plans":[{"id":"PLN-0015","file":"PLN-0015-propagate-dec-0009-into-studio-specs.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]},{"id":"PLN-0016","file":"PLN-0016-build-prometeu-vfs-filesystem-backed-core.md","status":"review","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]},{"id":"PLN-0017","file":"PLN-0017-add-studio-project-session-ownership-for-prometeu-vfs.md","status":"review","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]},{"id":"PLN-0018","file":"PLN-0018-migrate-code-editor-to-prometeu-vfs.md","status":"review","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0012","status":"open","ticket":"studio-editor-document-vfs-boundary","title":"Definir um boundary de VFS documental para tree/view/open files no Code Editor do Studio","created_at":"2026-03-31","updated_at":"2026-03-31","tags":["studio","editor","workspace","vfs","filesystem","boundary"],"agendas":[{"id":"AGD-0012","file":"AGD-0012-studio-editor-document-vfs-boundary.md","status":"in_progress","created_at":"2026-03-31","updated_at":"2026-03-31"}],"decisions":[{"id":"DEC-0009","file":"DEC-0009-studio-prometeu-vfs-project-document-boundary.md","status":"in_progress","created_at":"2026-03-31","updated_at":"2026-03-31","ref_agenda":"AGD-0012"}],"plans":[{"id":"PLN-0015","file":"PLN-0015-propagate-dec-0009-into-studio-specs.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]},{"id":"PLN-0016","file":"PLN-0016-build-prometeu-vfs-filesystem-backed-core.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]},{"id":"PLN-0017","file":"PLN-0017-add-studio-project-session-ownership-for-prometeu-vfs.md","status":"review","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]},{"id":"PLN-0018","file":"PLN-0018-migrate-code-editor-to-prometeu-vfs.md","status":"review","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0009"]}],"lessons":[]}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
id: PLN-0016
|
||||
ticket: studio-editor-document-vfs-boundary
|
||||
title: Build the filesystem-backed `prometeu-vfs` core for project tree and documents
|
||||
status: review
|
||||
status: done
|
||||
created: 2026-03-31
|
||||
completed:
|
||||
completed: 2026-03-31
|
||||
tags:
|
||||
- studio
|
||||
- vfs
|
||||
|
||||
@ -5,6 +5,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":prometeu-infra"))
|
||||
implementation(project(":prometeu-vfs"))
|
||||
implementation(project(":prometeu-packer:prometeu-packer-api"))
|
||||
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
||||
implementation(project(":prometeu-compiler:prometeu-build-pipeline"))
|
||||
|
||||
8
prometeu-vfs/build.gradle.kts
Normal file
8
prometeu-vfs/build.gradle.kts
Normal file
@ -0,0 +1,8 @@
|
||||
plugins {
|
||||
id("gradle.java-library-conventions")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
||||
implementation(project(":prometeu-compiler:prometeu-frontend-registry"))
|
||||
}
|
||||
@ -0,0 +1,209 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import p.studio.compiler.FrontendRegistryService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
final class FilesystemProjectDocumentVfs implements ProjectDocumentVfs {
|
||||
private final VfsProjectContext projectContext;
|
||||
private VfsProjectSnapshot snapshot;
|
||||
|
||||
FilesystemProjectDocumentVfs(final VfsProjectContext projectContext) {
|
||||
this.projectContext = Objects.requireNonNull(projectContext, "projectContext");
|
||||
this.snapshot = buildSnapshot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VfsProjectContext projectContext() {
|
||||
return projectContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VfsProjectSnapshot snapshot() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VfsProjectSnapshot refresh() {
|
||||
snapshot = buildSnapshot();
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VfsProjectSnapshot refresh(final VfsRefreshRequest request) {
|
||||
final Path refreshTarget = refreshRootFor(Objects.requireNonNull(request, "request").targetPath());
|
||||
if (refreshTarget.equals(projectContext.rootPath())) {
|
||||
return refresh();
|
||||
}
|
||||
|
||||
final Set<Path> taggedRoots = taggedSourceRoots();
|
||||
final String displayName = refreshTarget.getFileName() == null
|
||||
? projectContext.projectName()
|
||||
: refreshTarget.getFileName().toString();
|
||||
final VfsProjectNode refreshedNode = buildNode(refreshTarget, displayName, taggedRoots);
|
||||
snapshot = new VfsProjectSnapshot(
|
||||
projectContext.rootPath(),
|
||||
replaceNode(snapshot.rootNode(), refreshedNode));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VfsDocumentOpenResult openDocument(final Path path) {
|
||||
final Path normalizedPath = normalize(path);
|
||||
if (!normalizedPath.startsWith(projectContext.rootPath())) {
|
||||
return new VfsUnsupportedDocument(normalizedPath, VfsUnsupportedReason.OUTSIDE_PROJECT);
|
||||
}
|
||||
if (!Files.exists(normalizedPath)) {
|
||||
return new VfsUnsupportedDocument(normalizedPath, VfsUnsupportedReason.NOT_FOUND);
|
||||
}
|
||||
if (!Files.isRegularFile(normalizedPath)) {
|
||||
return new VfsUnsupportedDocument(normalizedPath, VfsUnsupportedReason.NOT_A_FILE);
|
||||
}
|
||||
|
||||
final byte[] bytes;
|
||||
try {
|
||||
bytes = Files.readAllBytes(normalizedPath);
|
||||
} catch (IOException ioException) {
|
||||
throw new UncheckedIOException(ioException);
|
||||
}
|
||||
|
||||
if (containsNulByte(bytes)) {
|
||||
return new VfsUnsupportedDocument(normalizedPath, VfsUnsupportedReason.BINARY_CONTENT);
|
||||
}
|
||||
|
||||
final String content;
|
||||
try {
|
||||
content = StandardCharsets.UTF_8.newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.decode(ByteBuffer.wrap(bytes))
|
||||
.toString();
|
||||
} catch (CharacterCodingException codingException) {
|
||||
return new VfsUnsupportedDocument(normalizedPath, VfsUnsupportedReason.INVALID_UTF8);
|
||||
}
|
||||
|
||||
return new VfsTextDocument(
|
||||
normalizedPath,
|
||||
normalizedPath.getFileName().toString(),
|
||||
content,
|
||||
lineSeparator(content));
|
||||
}
|
||||
|
||||
private VfsProjectSnapshot buildSnapshot() {
|
||||
return new VfsProjectSnapshot(
|
||||
projectContext.rootPath(),
|
||||
buildNode(projectContext.rootPath(), projectContext.projectName(), taggedSourceRoots()));
|
||||
}
|
||||
|
||||
private Set<Path> taggedSourceRoots() {
|
||||
return FrontendRegistryService.getFrontendSpec(projectContext.languageId())
|
||||
.stream()
|
||||
.flatMap(frontendSpec -> frontendSpec.getSourceRoots().stream())
|
||||
.map(projectContext.rootPath()::resolve)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::normalize)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private VfsProjectNode buildNode(
|
||||
final Path path,
|
||||
final String displayName,
|
||||
final Set<Path> taggedRoots) {
|
||||
if (!Files.isDirectory(path)) {
|
||||
return new VfsProjectNode(path, displayName, false, taggedRoots.contains(path), List.of());
|
||||
}
|
||||
|
||||
final List<VfsProjectNode> children;
|
||||
try (Stream<Path> stream = Files.list(path)) {
|
||||
children = stream
|
||||
.sorted(nodeComparator())
|
||||
.map(child -> buildNode(child, child.getFileName().toString(), taggedRoots))
|
||||
.toList();
|
||||
} catch (IOException ioException) {
|
||||
throw new UncheckedIOException(ioException);
|
||||
}
|
||||
|
||||
return new VfsProjectNode(path, displayName, true, taggedRoots.contains(path), children);
|
||||
}
|
||||
|
||||
private VfsProjectNode replaceNode(final VfsProjectNode currentNode, final VfsProjectNode replacementNode) {
|
||||
if (currentNode.path().equals(replacementNode.path())) {
|
||||
return replacementNode;
|
||||
}
|
||||
if (!replacementNode.path().startsWith(currentNode.path())) {
|
||||
return currentNode;
|
||||
}
|
||||
if (!currentNode.directory()) {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
final List<VfsProjectNode> replacedChildren = currentNode.children()
|
||||
.stream()
|
||||
.map(child -> replaceNode(child, replacementNode))
|
||||
.sorted(projectNodeComparator())
|
||||
.toList();
|
||||
return new VfsProjectNode(
|
||||
currentNode.path(),
|
||||
currentNode.displayName(),
|
||||
true,
|
||||
currentNode.taggedSourceRoot(),
|
||||
replacedChildren);
|
||||
}
|
||||
|
||||
private Path refreshRootFor(final Path targetPath) {
|
||||
final Path normalizedTarget = normalize(targetPath);
|
||||
if (!normalizedTarget.startsWith(projectContext.rootPath())) {
|
||||
throw new IllegalArgumentException("Refresh target must stay within the project root.");
|
||||
}
|
||||
if (Files.exists(normalizedTarget)) {
|
||||
return normalizedTarget;
|
||||
}
|
||||
final Path parent = normalizedTarget.getParent();
|
||||
if (parent == null || !parent.startsWith(projectContext.rootPath())) {
|
||||
return projectContext.rootPath();
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
private Comparator<Path> nodeComparator() {
|
||||
return Comparator
|
||||
.comparing((Path path) -> !Files.isDirectory(path))
|
||||
.thenComparing(path -> path.getFileName().toString(), String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
private Comparator<VfsProjectNode> projectNodeComparator() {
|
||||
return Comparator
|
||||
.comparing((VfsProjectNode node) -> !node.directory())
|
||||
.thenComparing(VfsProjectNode::displayName, String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
private Path normalize(final Path path) {
|
||||
return Objects.requireNonNull(path, "path").toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
private boolean containsNulByte(final byte[] bytes) {
|
||||
for (final byte value : bytes) {
|
||||
if (value == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String lineSeparator(final String content) {
|
||||
return content.contains("\r\n") ? "CRLF" : "LF";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FilesystemProjectDocumentVfsFactory implements ProjectDocumentVfsFactory {
|
||||
@Override
|
||||
public ProjectDocumentVfs open(final VfsProjectContext projectContext) {
|
||||
return new FilesystemProjectDocumentVfs(Objects.requireNonNull(projectContext, "projectContext"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface ProjectDocumentVfs extends AutoCloseable {
|
||||
VfsProjectContext projectContext();
|
||||
|
||||
VfsProjectSnapshot snapshot();
|
||||
|
||||
VfsProjectSnapshot refresh();
|
||||
|
||||
VfsProjectSnapshot refresh(VfsRefreshRequest request);
|
||||
|
||||
VfsDocumentOpenResult openDocument(Path path);
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
public interface ProjectDocumentVfsFactory {
|
||||
ProjectDocumentVfs open(VfsProjectContext projectContext);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public sealed interface VfsDocumentOpenResult permits VfsTextDocument, VfsUnsupportedDocument {
|
||||
Path path();
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public record VfsProjectContext(
|
||||
String projectName,
|
||||
String languageId,
|
||||
Path rootPath) {
|
||||
|
||||
public VfsProjectContext {
|
||||
Objects.requireNonNull(projectName, "projectName");
|
||||
Objects.requireNonNull(languageId, "languageId");
|
||||
rootPath = Objects.requireNonNull(rootPath, "rootPath").toAbsolutePath().normalize();
|
||||
}
|
||||
}
|
||||
19
prometeu-vfs/src/main/java/p/studio/vfs/VfsProjectNode.java
Normal file
19
prometeu-vfs/src/main/java/p/studio/vfs/VfsProjectNode.java
Normal file
@ -0,0 +1,19 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record VfsProjectNode(
|
||||
Path path,
|
||||
String displayName,
|
||||
boolean directory,
|
||||
boolean taggedSourceRoot,
|
||||
List<VfsProjectNode> children) {
|
||||
|
||||
public VfsProjectNode {
|
||||
path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize();
|
||||
Objects.requireNonNull(displayName, "displayName");
|
||||
children = List.copyOf(Objects.requireNonNull(children, "children"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public record VfsProjectSnapshot(
|
||||
Path projectRoot,
|
||||
VfsProjectNode rootNode) {
|
||||
|
||||
public VfsProjectSnapshot {
|
||||
projectRoot = Objects.requireNonNull(projectRoot, "projectRoot").toAbsolutePath().normalize();
|
||||
Objects.requireNonNull(rootNode, "rootNode");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public record VfsRefreshRequest(Path targetPath) {
|
||||
public VfsRefreshRequest {
|
||||
targetPath = Objects.requireNonNull(targetPath, "targetPath").toAbsolutePath().normalize();
|
||||
}
|
||||
}
|
||||
18
prometeu-vfs/src/main/java/p/studio/vfs/VfsTextDocument.java
Normal file
18
prometeu-vfs/src/main/java/p/studio/vfs/VfsTextDocument.java
Normal file
@ -0,0 +1,18 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public record VfsTextDocument(
|
||||
Path path,
|
||||
String documentName,
|
||||
String content,
|
||||
String lineSeparator) implements VfsDocumentOpenResult {
|
||||
|
||||
public VfsTextDocument {
|
||||
path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize();
|
||||
Objects.requireNonNull(documentName, "documentName");
|
||||
Objects.requireNonNull(content, "content");
|
||||
Objects.requireNonNull(lineSeparator, "lineSeparator");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public record VfsUnsupportedDocument(
|
||||
Path path,
|
||||
VfsUnsupportedReason reason) implements VfsDocumentOpenResult {
|
||||
|
||||
public VfsUnsupportedDocument {
|
||||
path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize();
|
||||
Objects.requireNonNull(reason, "reason");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
public enum VfsUnsupportedReason {
|
||||
OUTSIDE_PROJECT,
|
||||
NOT_FOUND,
|
||||
NOT_A_FILE,
|
||||
NO_HANDLER,
|
||||
BINARY_CONTENT,
|
||||
INVALID_UTF8
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package p.studio.vfs;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class FilesystemProjectDocumentVfsTest {
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void snapshotIncludesHiddenFilesOrdersFoldersFirstAndTagsSourceRoots() throws Exception {
|
||||
Files.createDirectories(tempDir.resolve("src"));
|
||||
Files.createDirectories(tempDir.resolve("assets"));
|
||||
Files.writeString(tempDir.resolve(".env"), "TOKEN=1\n");
|
||||
Files.writeString(tempDir.resolve("README.md"), "# project\n");
|
||||
|
||||
final ProjectDocumentVfs vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext());
|
||||
|
||||
final VfsProjectSnapshot snapshot = vfs.snapshot();
|
||||
|
||||
assertEquals("Example", snapshot.rootNode().displayName());
|
||||
assertEquals("assets", snapshot.rootNode().children().get(0).displayName());
|
||||
assertEquals("src", snapshot.rootNode().children().get(1).displayName());
|
||||
assertEquals(".env", snapshot.rootNode().children().get(2).displayName());
|
||||
assertTrue(snapshot.rootNode().children().get(1).taggedSourceRoot());
|
||||
}
|
||||
|
||||
@Test
|
||||
void openDocumentReturnsTextDocumentForUtf8TextFile() throws Exception {
|
||||
final Path file = tempDir.resolve("main.pbs");
|
||||
Files.writeString(file, "fn main(): void\n");
|
||||
|
||||
final ProjectDocumentVfs vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext());
|
||||
|
||||
final VfsDocumentOpenResult result = vfs.openDocument(file);
|
||||
final VfsTextDocument document = assertInstanceOf(VfsTextDocument.class, result);
|
||||
|
||||
assertEquals("main.pbs", document.documentName());
|
||||
assertEquals("LF", document.lineSeparator());
|
||||
assertTrue(document.content().contains("fn main()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openDocumentRejectsBinaryLikeFiles() throws Exception {
|
||||
final Path file = tempDir.resolve("sprite.bin");
|
||||
Files.write(file, new byte[]{0x01, 0x00, 0x02});
|
||||
|
||||
final ProjectDocumentVfs vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext());
|
||||
|
||||
final VfsDocumentOpenResult result = vfs.openDocument(file);
|
||||
final VfsUnsupportedDocument unsupported = assertInstanceOf(VfsUnsupportedDocument.class, result);
|
||||
|
||||
assertEquals(VfsUnsupportedReason.BINARY_CONTENT, unsupported.reason());
|
||||
}
|
||||
|
||||
@Test
|
||||
void openDocumentRejectsPathsOutsideProjectScope() throws Exception {
|
||||
final Path outsideFile = tempDir.getParent().resolve("outside.pbs");
|
||||
Files.writeString(outsideFile, "fn stray(): void\n");
|
||||
|
||||
final ProjectDocumentVfs vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext());
|
||||
|
||||
final VfsDocumentOpenResult result = vfs.openDocument(outsideFile);
|
||||
final VfsUnsupportedDocument unsupported = assertInstanceOf(VfsUnsupportedDocument.class, result);
|
||||
|
||||
assertEquals(VfsUnsupportedReason.OUTSIDE_PROJECT, unsupported.reason());
|
||||
}
|
||||
|
||||
@Test
|
||||
void targetedRefreshUpdatesOnlyTheRequestedSubtreeInTheSnapshot() throws Exception {
|
||||
final Path src = Files.createDirectories(tempDir.resolve("src"));
|
||||
Files.createDirectories(tempDir.resolve("assets"));
|
||||
Files.writeString(src.resolve("main.pbs"), "fn main(): void\n");
|
||||
|
||||
final ProjectDocumentVfs vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext());
|
||||
Files.writeString(src.resolve("later.pbs"), "fn later(): void\n");
|
||||
|
||||
final VfsProjectSnapshot refreshedSnapshot = vfs.refresh(new VfsRefreshRequest(src));
|
||||
final VfsProjectNode srcNode = refreshedSnapshot.rootNode().children().get(1);
|
||||
|
||||
assertEquals("src", srcNode.displayName());
|
||||
assertEquals(2, srcNode.children().size());
|
||||
assertEquals("later.pbs", srcNode.children().get(0).displayName());
|
||||
assertEquals("main.pbs", srcNode.children().get(1).displayName());
|
||||
}
|
||||
|
||||
private VfsProjectContext projectContext() {
|
||||
return new VfsProjectContext("Example", "pbs", tempDir);
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ plugins {
|
||||
rootProject.name = "prometeu-studio"
|
||||
|
||||
include("prometeu-infra")
|
||||
include("prometeu-vfs")
|
||||
include("prometeu-lsp:prometeu-lsp-api")
|
||||
include("prometeu-lsp:prometeu-lsp-v1")
|
||||
include("prometeu-packer:prometeu-packer-api")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user