diff --git a/src/manimShell.ts b/src/manimShell.ts index 7937281e..a16bf724 100644 --- a/src/manimShell.ts +++ b/src/manimShell.ts @@ -17,6 +17,14 @@ const ANSI_CONTROL_SEQUENCE_REGEX = /(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x */ const IPYTHON_CELL_START_REGEX = /^\s*In \[\d+\]:/gm; +/** + * Regular expression to match an info message in the terminal, + * e.g. `[17:07:34] INFO`. This is used to detect when the end is reached + * when Manim was started not in the interactive embedded mode, but in a way + * to preview the whole scene (without the `-se ` argument). + */ +const LOG_INFO_MESSAGE_REGEX = /^\s*\[.*\] INFO/m; + /** * Regular expression to match IPython multiline input "...:" * Sometimes IPython does not execute code when entering a newline, but enters a @@ -51,6 +59,11 @@ enum ManimShellEvent { */ IPYTHON_CELL_FINISHED = 'ipythonCellFinished', + /** + * Event emitted when a log info message is detected in the terminal. + */ + LOG_INFO_MESSAGE = 'logInfoMessage', + /** * Event emitted when a keyboard interrupt is detected in the terminal, e.g. * when `Ctrl+C` is pressed to stop the current command execution. @@ -346,8 +359,14 @@ export class ManimShell { * because another command needed to have a new shell started. * Only if the user manually starts a new scene, we want to exit a * potentially already running scene beforehand. + * @param shouldPreviewWholeScene Whether the command requests to preview + * the whole scene, i.e. without the `-se ` argument. In this + * case, we wait until an info message is shown in the terminal to detect + * when the whole scene has been previewed. Otherwise, we wait until the + * first IPython cell is found. */ - public async executeStartCommand(command: string, isRequestedForAnotherCommand: boolean) { + public async executeStartCommand(command: string, + isRequestedForAnotherCommand: boolean, shouldPreviewWholeScene: boolean) { if (!isRequestedForAnotherCommand) { Logger.debug("🔆 Executing start command that is requested for its own"); if (this.hasActiveShell()) { @@ -368,9 +387,14 @@ export class ManimShell { Logger.debug("🔆 Executing start command that is requested for another command"); } + if (shouldPreviewWholeScene) { + Logger.debug("🥽 Whole scene preview requested"); + } + await window.withProgress({ location: vscode.ProgressLocation.Notification, - title: "Starting Manim...", + title: shouldPreviewWholeScene + ? "Previewing whole scene..." : "Starting Manim...", cancellable: false }, async (progress, token) => { // We are sure that the active shell is set since it is invoked @@ -378,7 +402,9 @@ export class ManimShell { this.shellWeTryToSpawnIn = this.activeShell; this.exec(this.activeShell as Terminal, command); - const commandFinishedPromise = this.waitUntilCommandFinished(this.iPythonCellCount); + const commandFinishedPromise = shouldPreviewWholeScene + ? this.waitUntilInfoMessageShown() + : this.waitUntilCommandFinished(this.iPythonCellCount); const manimNotStartedPromise = new Promise(resolve => { this.eventEmitter.once(ManimShellEvent.MANIM_NOT_STARTED, resolve); }); @@ -575,6 +601,8 @@ export class ManimShell { * command has actually finished executing, e.g. when the whole animation * has been previewed. * + * Can be interrupted by a keyboard interrupt. + * * @param currentExecutionCount The current IPython cell count when the * command was issued. This is used to detect when the next cell has started. * @param callback An optional callback that is invoked when the command @@ -605,6 +633,18 @@ export class ManimShell { } } + /** + * Waits until an info message is shown in the terminal. + * + * Can be interrupted by a keyboard interrupt. + */ + private async waitUntilInfoMessageShown() { + await Promise.race([ + new Promise(resolve => this.eventEmitter.once(ManimShellEvent.KEYBOARD_INTERRUPT, resolve)), + new Promise(resolve => this.eventEmitter.once(ManimShellEvent.LOG_INFO_MESSAGE, resolve)) + ]); + } + private async sendKeyboardInterrupt() { Logger.debug("💨 Sending keyboard interrupt to terminal"); await this.activeShell?.sendText('\x03'); // send `Ctrl+C` @@ -655,6 +695,11 @@ export class ManimShell { this.eventEmitter.emit(ManimShellEvent.KEYBOARD_INTERRUPT); } + if (data.match(LOG_INFO_MESSAGE_REGEX)) { + Logger.debug("📜 Log info message detected"); + this.eventEmitter.emit(ManimShellEvent.LOG_INFO_MESSAGE); + } + let ipythonMatches = data.match(IPYTHON_CELL_START_REGEX); if (ipythonMatches) { // Terminal data might include multiple IPython statements, diff --git a/src/startStopScene.ts b/src/startStopScene.ts index 17a3de9b..6368455f 100644 --- a/src/startStopScene.ts +++ b/src/startStopScene.ts @@ -72,36 +72,18 @@ export async function startScene(lineStart?: number) { // Create the command const filePath = editor.document.fileName; // absolute path const cmds = ["manimgl", `"${filePath}"`, sceneName]; - let enter = false; + let shouldPreviewWholeScene = true; if (cursorLine !== matchingClass.index) { + // this is actually the more common case + shouldPreviewWholeScene = false; cmds.push(`-se ${lineNumber + 1}`); - enter = true; } const command = cmds.join(" "); - // // Commented out - in case someone would like it. - // // For us - we want to NOT overwrite our clipboard. - // // If one wants to run it in a different terminal, - // // it's often to write to a file - // await vscode.env.clipboard.writeText(command + " --prerun --finder -w"); - // Run the command const isRequestedForAnotherCommand = (lineStart !== undefined); - await ManimShell.instance.executeStartCommand(command, isRequestedForAnotherCommand); - - // // Commented out - in case someone would like it. - // // For us - it would require MacOS. Also - the effect is not desired. - // // Focus some windows (ONLY for MacOS because it uses `osascript`!) - // const terminal = window.activeTerminal || window.createTerminal(); - // if (enter) { - // // Keep cursor where it started (in VSCode) - // const cmd_focus_vscode = 'osascript -e "tell application \\"Visual Studio Code\\" to activate"'; - // // Execute the command in the shell after a delay (to give the animation window enough time to open) - // await new Promise(resolve => setTimeout(resolve, 2500)); - // require('child_process').exec(cmd_focus_vscode); - // } else { - // terminal.show(); - // } + await ManimShell.instance.executeStartCommand( + command, isRequestedForAnotherCommand, shouldPreviewWholeScene); } /**