implements PLN-0043 debug workspace first wave

This commit is contained in:
bQUARKz 2026-04-06 06:41:17 +01:00
parent 56be8fa69e
commit 16503b2575
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 225 additions and 3 deletions

6
.gitignore vendored
View File

@ -10,7 +10,11 @@ build
# Ignore Kotlin plugin data # Ignore Kotlin plugin data
.kotlin .kotlin
debug /debug/
!prometeu-studio/src/main/java/p/studio/workspaces/debug/
!prometeu-studio/src/main/java/p/studio/workspaces/debug/**
!prometeu-studio/src/test/java/p/studio/workspaces/debug/
!prometeu-studio/src/test/java/p/studio/workspaces/debug/**
**/.studio/** **/.studio/**

View File

@ -114,6 +114,15 @@ public enum I18n {
WORKSPACE_SHIPPER_LOGS("workspace.shipper.logs"), WORKSPACE_SHIPPER_LOGS("workspace.shipper.logs"),
WORKSPACE_SHIPPER_BUTTON_RUN("workspace.shipper.button.run"), WORKSPACE_SHIPPER_BUTTON_RUN("workspace.shipper.button.run"),
WORKSPACE_SHIPPER_BUTTON_CLEAR("workspace.shipper.button.clear"), WORKSPACE_SHIPPER_BUTTON_CLEAR("workspace.shipper.button.clear"),
WORKSPACE_DEBUG_STATE("workspace.debug.state"),
WORKSPACE_DEBUG_LOGS("workspace.debug.logs"),
WORKSPACE_DEBUG_STATUS_IDLE("workspace.debug.status.idle"),
WORKSPACE_DEBUG_STATUS_PREPARING("workspace.debug.status.preparing"),
WORKSPACE_DEBUG_STATUS_PREPARE_FAILED("workspace.debug.status.prepareFailed"),
WORKSPACE_DEBUG_STATUS_CONNECTING("workspace.debug.status.connecting"),
WORKSPACE_DEBUG_STATUS_RUNNING("workspace.debug.status.running"),
WORKSPACE_DEBUG_STATUS_RUNTIME_FAILED("workspace.debug.status.runtimeFailed"),
WORKSPACE_DEBUG_STATUS_STOPPED("workspace.debug.status.stopped"),
WORKSPACE_ASSETS("workspace.assets"), WORKSPACE_ASSETS("workspace.assets"),
ASSETS_NAVIGATOR_TITLE("assets.navigator.title"), ASSETS_NAVIGATOR_TITLE("assets.navigator.title"),

View File

@ -13,6 +13,7 @@ import p.studio.workspaces.WorkspaceHost;
import p.studio.workspaces.WorkspaceId; import p.studio.workspaces.WorkspaceId;
import p.studio.workspaces.assets.AssetWorkspace; import p.studio.workspaces.assets.AssetWorkspace;
import p.studio.workspaces.builder.ShipperWorkspace; import p.studio.workspaces.builder.ShipperWorkspace;
import p.studio.workspaces.debug.DebugWorkspace;
import p.studio.workspaces.editor.EditorWorkspace; import p.studio.workspaces.editor.EditorWorkspace;
import java.util.List; import java.util.List;
@ -49,7 +50,7 @@ public final class MainView extends BorderPane {
editorWorkspace.restoreProjectLocalState(persistedState); editorWorkspace.restoreProjectLocalState(persistedState);
assetWorkspace.setStateChangedAction(this::persistProjectLocalState); assetWorkspace.setStateChangedAction(this::persistProjectLocalState);
editorWorkspace.setStateChangedAction(this::persistProjectLocalState); editorWorkspace.setStateChangedAction(this::persistProjectLocalState);
// host.register(new PlaceholderWorkspace(WorkspaceId.DEBUG, I18n.WORKSPACE_DEBUG, "Debug")); host.register(new DebugWorkspace(projectReference, projectSession.executionSession()));
host.register(new ShipperWorkspace(projectReference)); host.register(new ShipperWorkspace(projectReference));
workspaceRail = new StudioWorkspaceRailControl<>( workspaceRail = new StudioWorkspaceRailControl<>(

View File

@ -0,0 +1,116 @@
package p.studio.workspaces.debug;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import p.studio.Container;
import p.studio.execution.StudioExecutionSessionService;
import p.studio.execution.StudioExecutionSnapshot;
import p.studio.execution.StudioExecutionState;
import p.studio.projects.ProjectReference;
import p.studio.utilities.events.EventSubscription;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.Workspace;
import p.studio.workspaces.WorkspaceId;
public final class DebugWorkspace extends Workspace {
private final BorderPane root = new BorderPane();
private final StudioExecutionSessionService executionSessionService;
private final Label statusValue = new Label();
private final TextArea logs = new TextArea();
private EventSubscription sessionSubscription;
public DebugWorkspace(
final ProjectReference projectReference,
final StudioExecutionSessionService executionSessionService) {
super(projectReference);
this.executionSessionService = executionSessionService;
root.getStyleClass().add("debug-workspace");
root.setPadding(new Insets(16));
root.setTop(buildStatusPane());
root.setCenter(buildLogsPane());
}
@Override
public WorkspaceId workspaceId() {
return WorkspaceId.DEBUG;
}
@Override
public I18n title() {
return I18n.WORKSPACE_DEBUG;
}
@Override
public Node rootNode() {
return root;
}
@Override
public void load() {
if (sessionSubscription != null) {
return;
}
sessionSubscription = executionSessionService.subscribe(this::renderSnapshot);
}
@Override
public void unLoad() {
if (sessionSubscription == null) {
return;
}
sessionSubscription.unsubscribe();
sessionSubscription = null;
}
private Node buildStatusPane() {
final Label title = new Label();
title.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_DEBUG_STATE));
title.getStyleClass().add("studio-section-title");
statusValue.getStyleClass().add("studio-debug-status-value");
return new HBox(12, title, statusValue);
}
private Node buildLogsPane() {
logs.setEditable(false);
logs.setWrapText(true);
logs.setMinHeight(360);
final TitledPane pane = new TitledPane();
pane.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_DEBUG_LOGS));
pane.setContent(logs);
pane.setCollapsible(false);
pane.setExpanded(true);
return pane;
}
private void renderSnapshot(final StudioExecutionSnapshot snapshot) {
if (Platform.isFxApplicationThread()) {
applySnapshot(snapshot);
return;
}
Platform.runLater(() -> applySnapshot(snapshot));
}
private void applySnapshot(final StudioExecutionSnapshot snapshot) {
final DebugWorkspaceProjection projection = DebugWorkspaceProjection.from(snapshot);
statusValue.setText(Container.i18n().text(statusText(projection.state())));
logs.setText(String.join(System.lineSeparator(), projection.renderedLogs()));
}
private I18n statusText(final StudioExecutionState state) {
return switch (state) {
case IDLE -> I18n.WORKSPACE_DEBUG_STATUS_IDLE;
case PREPARING -> I18n.WORKSPACE_DEBUG_STATUS_PREPARING;
case PREPARE_FAILED -> I18n.WORKSPACE_DEBUG_STATUS_PREPARE_FAILED;
case CONNECTING -> I18n.WORKSPACE_DEBUG_STATUS_CONNECTING;
case RUNNING -> I18n.WORKSPACE_DEBUG_STATUS_RUNNING;
case RUNTIME_FAILED -> I18n.WORKSPACE_DEBUG_STATUS_RUNTIME_FAILED;
case STOPPED -> I18n.WORKSPACE_DEBUG_STATUS_STOPPED;
};
}
}

View File

@ -0,0 +1,32 @@
package p.studio.workspaces.debug;
import p.studio.execution.StudioExecutionLogEntry;
import p.studio.execution.StudioExecutionSnapshot;
import p.studio.execution.StudioExecutionState;
import java.util.List;
import java.util.Objects;
public record DebugWorkspaceProjection(
StudioExecutionState state,
List<String> renderedLogs) {
public DebugWorkspaceProjection {
Objects.requireNonNull(state, "state");
renderedLogs = List.copyOf(Objects.requireNonNull(renderedLogs, "renderedLogs"));
}
public static DebugWorkspaceProjection from(final StudioExecutionSnapshot snapshot) {
final StudioExecutionSnapshot safeSnapshot = Objects.requireNonNull(snapshot, "snapshot");
return new DebugWorkspaceProjection(
safeSnapshot.state(),
safeSnapshot.logs().stream().map(DebugWorkspaceProjection::renderLog).toList());
}
private static String renderLog(final StudioExecutionLogEntry entry) {
return "[%s][%s] %s".formatted(
entry.source().name(),
entry.severity().name(),
entry.message());
}
}

View File

@ -104,6 +104,15 @@ workspace.shipper=Shipper
workspace.shipper.logs=Logs workspace.shipper.logs=Logs
workspace.shipper.button.run=Build workspace.shipper.button.run=Build
workspace.shipper.button.clear=Clear workspace.shipper.button.clear=Clear
workspace.debug.state=State
workspace.debug.logs=Execution Logs
workspace.debug.status.idle=Idle
workspace.debug.status.preparing=Preparing
workspace.debug.status.prepareFailed=Preparation failed
workspace.debug.status.connecting=Connecting
workspace.debug.status.running=Running
workspace.debug.status.runtimeFailed=Runtime failed
workspace.debug.status.stopped=Stopped
workspace.assets=Assets workspace.assets=Assets
assets.navigator.title=Asset Navigator assets.navigator.title=Asset Navigator

View File

@ -19,4 +19,13 @@ workspace.shipper.button.run=Construir
workspace.shipper.button.clear=Limpar workspace.shipper.button.clear=Limpar
workspace.assets=Assets workspace.assets=Assets
workspace.debug=Depurar workspace.debug=Depurar
workspace.debug.state=Estado
workspace.debug.logs=Logs de Execucao
workspace.debug.status.idle=Idle
workspace.debug.status.preparing=Preparando
workspace.debug.status.prepareFailed=Preparacao falhou
workspace.debug.status.connecting=Conectando
workspace.debug.status.running=Executando
workspace.debug.status.runtimeFailed=Runtime falhou
workspace.debug.status.stopped=Parado

View File

@ -0,0 +1,42 @@
package p.studio.workspaces.debug;
import org.junit.jupiter.api.Test;
import p.studio.execution.StudioExecutionLogEntry;
import p.studio.execution.StudioExecutionLogSeverity;
import p.studio.execution.StudioExecutionLogSource;
import p.studio.execution.StudioExecutionSnapshot;
import p.studio.execution.StudioExecutionState;
import java.time.Instant;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
final class DebugWorkspaceProjectionTest {
@Test
void preservesSessionStateAndVisibleLogOrigins() {
final StudioExecutionSnapshot snapshot = new StudioExecutionSnapshot(
StudioExecutionState.PREPARING,
List.of(
new StudioExecutionLogEntry(
1L,
Instant.parse("2026-04-06T10:00:00Z"),
StudioExecutionLogSource.BUILD,
StudioExecutionLogSeverity.INFO,
"Compiler started"),
new StudioExecutionLogEntry(
2L,
Instant.parse("2026-04-06T10:00:01Z"),
StudioExecutionLogSource.PACK,
StudioExecutionLogSeverity.ERROR,
"Pack failed")));
final DebugWorkspaceProjection projection = DebugWorkspaceProjection.from(snapshot);
assertEquals(StudioExecutionState.PREPARING, projection.state());
assertEquals(2, projection.renderedLogs().size());
assertTrue(projection.renderedLogs().getFirst().contains("[BUILD][INFO]"));
assertTrue(projection.renderedLogs().getLast().contains("[PACK][ERROR]"));
}
}