Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions src/manimShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <lineNumber>` 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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 <lineNumber>` 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()) {
Expand All @@ -368,17 +387,24 @@ 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
// in `retrieveOrInitActiveShell()` or in the line above.
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<void>(resolve => {
this.eventEmitter.once(ManimShellEvent.MANIM_NOT_STARTED, resolve);
});
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<void>(resolve => this.eventEmitter.once(ManimShellEvent.KEYBOARD_INTERRUPT, resolve)),
new Promise<void>(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`
Expand Down Expand Up @@ -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,
Expand Down
28 changes: 5 additions & 23 deletions src/startStopScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down