Skip to content

Commit 6cad6ec

Browse files
committed
Prevent multiple commands from running on MacOS
1 parent 4d41027 commit 6cad6ec

File tree

3 files changed

+60
-33
lines changed

3 files changed

+60
-33
lines changed

src/manimShell.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ enum ManimShellEvent {
3737
IPYTHON_CELL_FINISHED = 'ipythonCellFinished',
3838
}
3939

40+
const MAC_OS_MULTIPLE_COMMANDS_ERROR = `On MacOS, we don't support running`
41+
+ ` multiple Manim commands at the same time. Please wait until the`
42+
+ ` current command has finished.`;
43+
4044
/**
4145
* Wrapper around the IPython terminal that ManimGL uses. Ensures that commands
4246
* are executed at the right place and spans a new Manim session if necessary.
@@ -61,6 +65,15 @@ export class ManimShell {
6165
*/
6266
private lockDuringStartup = false;
6367

68+
/**
69+
* Whether to lock the execution of a new command while another command is
70+
* currently running. On MacOS, we do lock since the IPython terminal *exits*
71+
* when sending Ctrl+C instead of just interrupting the current command.
72+
* See issue #16: https://github.com/bhoov/manim-notebook/issues/16
73+
*/
74+
private shouldLockDuringCommandExecution = false;
75+
private isExecutingCommand = false;
76+
6477
/**
6578
* Whether to detect the end of a shell execution.
6679
*
@@ -78,6 +91,11 @@ export class ManimShell {
7891

7992
private constructor() {
8093
this.initiateTerminalDataReading();
94+
95+
// on MacOS
96+
if (process.platform === "darwin") {
97+
this.shouldLockDuringCommandExecution = true;
98+
}
8199
}
82100

83101
public static get instance(): ManimShell {
@@ -102,22 +120,36 @@ export class ManimShell {
102120
* @param [waitUntilFinished=false] Whether to wait until the actual command
103121
* has finished executing, e.g. when the whole animation has been previewed.
104122
*/
105-
public async executeCommand(command: string, startLine: number, waitUntilFinished = false) {
123+
public async executeCommand(command: string, startLine: number,
124+
waitUntilFinished = false, onCommandIssued?: () => void) {
106125
if (this.lockDuringStartup) {
107126
return vscode.window.showWarningMessage("Manim is currently starting. Please wait a moment.");
108127
}
128+
if (this.shouldLockDuringCommandExecution && this.isExecutingCommand) {
129+
return vscode.window.showWarningMessage(MAC_OS_MULTIPLE_COMMANDS_ERROR);
130+
}
131+
132+
this.isExecutingCommand = true;
109133

110134
this.lockDuringStartup = true;
111135
const shell = await this.retrieveOrInitActiveShell(startLine);
112136
this.lockDuringStartup = false;
113137

114138
this.exec(shell, command);
139+
if (onCommandIssued) {
140+
onCommandIssued();
141+
}
115142

116143
if (waitUntilFinished) {
117144
await new Promise(resolve => {
118145
this.eventEmitter.once(ManimShellEvent.IPYTHON_CELL_FINISHED, resolve);
119146
});
120147
}
148+
149+
this.eventEmitter.once(ManimShellEvent.IPYTHON_CELL_FINISHED, () => {
150+
console.log("Finished command execution.");
151+
this.isExecutingCommand = false;
152+
});
121153
}
122154

123155
/**
@@ -126,21 +158,34 @@ export class ManimShell {
126158
* @param command The command to execute in the VSCode terminal.
127159
* @param [waitUntilFinished=false] Whether to wait until the actual command
128160
* has finished executing, e.g. when the whole animation has been previewed.
161+
* @param [forceExecute=false] Whether to force the execution of the command
162+
* even if another command is currently running. This is only necessary when
163+
* the `shouldLockDuringCommandExecution` is set to true.
129164
* @returns A boolean indicating whether an active shell was found or not.
130165
* If no active shell was found, the command was also not executed.
131166
*/
132167
public async executeCommandEnsureActiveSession(
133-
command: string, waitUntilFinished = false): Promise<boolean> {
168+
command: string, waitUntilFinished = false, forceExecute = false): Promise<boolean> {
134169
if (!this.hasActiveShell()) {
135170
return Promise.resolve(false);
136171
}
172+
if (this.shouldLockDuringCommandExecution && !forceExecute && this.isExecutingCommand) {
173+
vscode.window.showWarningMessage(MAC_OS_MULTIPLE_COMMANDS_ERROR);
174+
return Promise.resolve(true);
175+
}
176+
177+
this.isExecutingCommand = true;
137178

138179
this.exec(this.activeShell as Terminal, command);
139180
if (waitUntilFinished) {
140181
await new Promise(resolve => {
141182
this.eventEmitter.once(ManimShellEvent.IPYTHON_CELL_FINISHED, resolve);
142183
});
143184
}
185+
186+
this.eventEmitter.once(ManimShellEvent.IPYTHON_CELL_FINISHED, () => {
187+
this.isExecutingCommand = false;
188+
});
144189
return Promise.resolve(true);
145190
}
146191

src/previewCode.ts

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,6 @@ const PREVIEW_COMMAND = `\x0C checkpoint_paste()\x1b`;
66
// \x0C: is Ctrl + L
77
// \x1b: https://github.com/bhoov/manim-notebook/issues/18#issuecomment-2431146809
88

9-
/**
10-
* Whether the extension is currently executing a Manim command.
11-
*
12-
* Note that this is not capturing whether the `checkpoint_paste()` command is
13-
* still running. Instead, it only captures whether reading/writing to clipboard
14-
* is currently happening to prevent unpredictable behavior.
15-
*
16-
* We don't need to capture the state of `checkpoint_paste()` because users
17-
* might actually want to preview a cell (or another one) again from the start
18-
* even though the animation is still running. With the new VSCode terminal
19-
* shell integration, it will automatically send a `Ctrl + C` to the terminal
20-
* when a new command is sent, so the previous command will be interrupted.
21-
*/
22-
let isExecuting = false;
23-
249
/**
2510
* Interactively previews the given Manim code by means of the
2611
* `checkpoint_paste()` method from Manim.
@@ -37,26 +22,22 @@ let isExecuting = false;
3722
* @param code The code to preview (e.g. from a Manim cell or from a custom selection).
3823
*/
3924
export async function previewCode(code: string, startLine: number): Promise<void> {
40-
if (isExecuting) {
41-
vscode.window.showInformationMessage('Please wait a few seconds, then try again.');
42-
return;
43-
}
44-
isExecuting = true;
45-
4625
try {
4726
const clipboardBuffer = await vscode.env.clipboard.readText();
4827
await vscode.env.clipboard.writeText(code);
4928

50-
await ManimShell.instance.executeCommand(PREVIEW_COMMAND, startLine);
51-
52-
// Restore original clipboard content
53-
const timeout = vscode.workspace.getConfiguration("manim-notebook").clipboardTimeout;
54-
setTimeout(async () => {
55-
await vscode.env.clipboard.writeText(clipboardBuffer);
56-
}, timeout);
29+
await ManimShell.instance.executeCommand(
30+
PREVIEW_COMMAND, startLine, true,
31+
() => restoreClipboard(clipboardBuffer)
32+
);
5733
} catch (error) {
5834
vscode.window.showErrorMessage(`Error: ${error}`);
59-
} finally {
60-
isExecuting = false;
6135
}
6236
}
37+
38+
function restoreClipboard(clipboardBuffer: string) {
39+
const timeout = vscode.workspace.getConfiguration("manim-notebook").clipboardTimeout;
40+
setTimeout(async () => {
41+
await vscode.env.clipboard.writeText(clipboardBuffer);
42+
}, timeout);
43+
}

src/startStopScene.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ export async function startScene(lineStart?: number) {
108108
* and the IPython terminal.
109109
*/
110110
export async function exitScene() {
111-
const success = await ManimShell.instance.executeCommandEnsureActiveSession("exit()");
111+
const success = await ManimShell.instance.executeCommandEnsureActiveSession(
112+
"exit()", false, true);
112113
if (success) {
113114
ManimShell.instance.resetActiveShell();
114115
} else {

0 commit comments

Comments
 (0)