diff --git a/.gitignore b/.gitignore index e4d1eb64..74bd999e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,11 @@ build # Ignore Kotlin plugin data .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/** diff --git a/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java index 8caa201c..2b5340df 100644 --- a/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java +++ b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java @@ -114,6 +114,15 @@ public enum I18n { WORKSPACE_SHIPPER_LOGS("workspace.shipper.logs"), WORKSPACE_SHIPPER_BUTTON_RUN("workspace.shipper.button.run"), 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"), ASSETS_NAVIGATOR_TITLE("assets.navigator.title"), diff --git a/prometeu-studio/src/main/java/p/studio/window/MainView.java b/prometeu-studio/src/main/java/p/studio/window/MainView.java index 53c0d49b..1b0fd59e 100644 --- a/prometeu-studio/src/main/java/p/studio/window/MainView.java +++ b/prometeu-studio/src/main/java/p/studio/window/MainView.java @@ -13,6 +13,7 @@ import p.studio.workspaces.WorkspaceHost; import p.studio.workspaces.WorkspaceId; import p.studio.workspaces.assets.AssetWorkspace; import p.studio.workspaces.builder.ShipperWorkspace; +import p.studio.workspaces.debug.DebugWorkspace; import p.studio.workspaces.editor.EditorWorkspace; import java.util.List; @@ -49,7 +50,7 @@ public final class MainView extends BorderPane { editorWorkspace.restoreProjectLocalState(persistedState); assetWorkspace.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)); workspaceRail = new StudioWorkspaceRailControl<>( diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/debug/DebugWorkspace.java b/prometeu-studio/src/main/java/p/studio/workspaces/debug/DebugWorkspace.java new file mode 100644 index 00000000..56b30dcd --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/debug/DebugWorkspace.java @@ -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; + }; + } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/debug/DebugWorkspaceProjection.java b/prometeu-studio/src/main/java/p/studio/workspaces/debug/DebugWorkspaceProjection.java new file mode 100644 index 00000000..3f81198e --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/debug/DebugWorkspaceProjection.java @@ -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 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()); + } +} diff --git a/prometeu-studio/src/main/resources/i18n/messages.properties b/prometeu-studio/src/main/resources/i18n/messages.properties index 998b6384..8597bcbb 100644 --- a/prometeu-studio/src/main/resources/i18n/messages.properties +++ b/prometeu-studio/src/main/resources/i18n/messages.properties @@ -104,6 +104,15 @@ workspace.shipper=Shipper workspace.shipper.logs=Logs workspace.shipper.button.run=Build 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 assets.navigator.title=Asset Navigator diff --git a/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties b/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties index 43b166d1..71ec4f02 100644 --- a/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties +++ b/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties @@ -19,4 +19,13 @@ workspace.shipper.button.run=Construir workspace.shipper.button.clear=Limpar workspace.assets=Assets -workspace.debug=Depurar \ No newline at end of file +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 diff --git a/prometeu-studio/src/test/java/p/studio/workspaces/debug/DebugWorkspaceProjectionTest.java b/prometeu-studio/src/test/java/p/studio/workspaces/debug/DebugWorkspaceProjectionTest.java new file mode 100644 index 00000000..35ff51e9 --- /dev/null +++ b/prometeu-studio/src/test/java/p/studio/workspaces/debug/DebugWorkspaceProjectionTest.java @@ -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]")); + } +}