Skip to content

Instantly share code, notes, and snippets.

@wallneradam
Last active October 4, 2025 09:52
Show Gist options
  • Select an option

  • Save wallneradam/87a05a47120301844850bc31326146f2 to your computer and use it in GitHub Desktop.

Select an option

Save wallneradam/87a05a47120301844850bc31326146f2 to your computer and use it in GitHub Desktop.
Proof that ZSH's ZLE is not active during precmd execution - MC cannot send escape sequences from precmd hook
=== Demonstration: Escape sequences don't work during precmd ===
Setting up precmd with: kill -STOP $$; sleep 2
This simulates precmd taking time to complete.
Waiting for setup to complete...
✓ Setup complete
Waiting for STOP signal (max 5 seconds)...
✓ Received STOP signal from precmd
Sending CONTINUE...
Waiting 500ms (precmd still has ~1500ms sleep remaining)...
Sending escape sequence while precmd is STILL running...
Reading response immediately (precmd still has ~1500ms left)...
Result:
---
^[t
---
✓ EXPECTED: Handler NOT executed
Escape sequence appeared literally (^[t) - ZLE was not active!
CONCLUSION:
Even after receiving STOP and sending CONTINUE, we cannot send
escape sequences until precmd COMPLETES and returns control to ZLE.
This proves MC cannot send key events from within the precmd hook!
The escape sequence must be sent AFTER precmd finishes and ZLE activates.
/*
* Demonstration: ZLE is not active while precmd hook is running
*
* This program proves that Midnight Commander cannot send escape sequences
* (key events) to ZSH during the precmd hook execution, even after receiving
* the SIGSTOP synchronization signal.
*
* The test:
* 1. Setup a precmd hook that sends SIGSTOP, then sleeps for 2 seconds
* (simulating real work like pwd output, prompt expansion, etc.)
* 2. Shell displays first prompt → precmd runs and sends STOP
* 3. MC receives STOP → sends CONTINUE
* 4. MC waits 500ms (precmd still has ~1.8 seconds sleep left)
* 5. MC sends escape sequence (ESC+t) to trigger a ZLE handler
*
* Expected result:
* - The escape sequence appears LITERALLY in output (^[t)
* - The handler is NOT called
* - This proves ZLE is not active until precmd completes
*
* Why this matters:
* - MC's current SIGSTOP-based synchronization only guarantees precmd started
* - It does NOT guarantee ZLE is ready to receive escape sequences
* - MC needs additional synchronization (e.g., zle-line-init hook) to know
* when ZLE is actually ready
*
* Platform behavior:
* - This test works identically on both Linux and macOS/BSD
* - The precmd runs once when the shell displays the first prompt
*
* Compile: gcc -o test_precmd_timing test_precmd_timing.c -lutil
* Run: ./test_precmd_timing
*/
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <signal.h>
#include <fcntl.h>
#ifdef __APPLE__
#include <util.h>
#else
#include <pty.h>
#endif
static int master_fd = -1;
static pid_t child_pid = -1;
static volatile sig_atomic_t child_stopped = 0;
static void sigchld_handler(int sig)
{
(void)sig;
int status;
if (waitpid(-1, &status, WNOHANG | WUNTRACED) > 0 && WIFSTOPPED(status))
child_stopped = 1;
}
static void read_available(char *buf, size_t size, int timeout_ms)
{
fd_set fds;
struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
FD_ZERO(&fds);
FD_SET(master_fd, &fds);
if (select(master_fd + 1, &fds, NULL, NULL, &tv) > 0)
read(master_fd, buf, size - 1);
}
int main(void)
{
struct sigaction sa = { .sa_handler = sigchld_handler, .sa_flags = SA_RESTART };
sigaction(SIGCHLD, &sa, NULL);
printf("=== Demonstration: Escape sequences don't work during precmd ===\n\n");
/* Fork ZSH with PTY (like MC does with openpty) */
child_pid = forkpty(&master_fd, NULL, NULL, NULL);
if (child_pid == 0)
{
setenv("PS1", "READY> ", 1);
setenv("ZDOTDIR", "/dev/null", 1);
/* Disable completion system to avoid compdump errors */
unsetenv("fpath");
execlp("zsh", "zsh", "-Z", "-g", "-f", NULL); /* -f: no rcs */
exit(1);
}
sleep(1);
/* Setup: precmd with STOP + 2 second sleep
*
* The sleep simulates any work precmd might do in real scenarios:
* - pwd output and pipe write (like MC does)
* - prompt expansion and evaluation
* - user's custom precmd hooks
*
* The key point: even after MC receives STOP, the precmd is still running,
* and ZLE is not yet active to process key bindings.
*
* Note: The precmd is registered immediately in the init script.
* It will run when ZSH displays the first prompt (platform-independent).
*/
printf("Setting up precmd with: kill -STOP $$; sleep 2\n");
printf("This simulates precmd taking time to complete.\n\n");
const char *init =
"echo -n '' >/tmp/precmd.log;\n"
"PROMPT='READY> '\n"
"handler() { echo '>>HANDLER_EXECUTED<<' }\n" /* Different string to avoid false positive */
"zle -N handler\n"
"bindkey '^[t' handler\n" /* Bind ESC+t to our test handler */
"echo 'SETUP_COMPLETE'\n" /* Print BEFORE setting up precmd */
"_precmd() { echo 'PRECMD_RUNNING' >>/tmp/precmd.log; kill -STOP $$; sleep 2; echo 'PRECMD_DONE' >>/tmp/precmd.log; };"
"precmd_functions+=(_precmd);\n";
write(master_fd, init, strlen(init));
/* Wait for setup to complete */
printf("Waiting for setup to complete...\n");
char drain[4096] = {0};
int setup_done = 0;
for (int i = 0; i < 30 && !setup_done; i++)
{
usleep(100000);
char tmp[1024] = {0};
read_available(tmp, sizeof(tmp), 100);
if (strlen(tmp) > 0)
{
strncat(drain, tmp, sizeof(drain) - strlen(drain) - 1);
}
if (strstr(drain, "SETUP_COMPLETE"))
setup_done = 1;
}
if (!setup_done)
{
printf("✗ Setup failed - ZSH did not respond\n");
kill(child_pid, SIGKILL);
return 1;
}
printf("✓ Setup complete\n\n");
/* Wait for STOP with timeout and read output */
printf("Waiting for STOP signal (max 5 seconds)...\n");
char output[4096] = {0};
int wait_count = 0;
while (!child_stopped && wait_count++ < 50)
{
usleep(100000); /* 100ms */
char tmp[1024] = {0};
read_available(tmp, sizeof(tmp), 10);
if (strlen(tmp) > 0)
{
strncat(output, tmp, sizeof(output) - strlen(output) - 1);
}
}
if (!child_stopped)
{
printf("✗ ERROR: Never received STOP signal!\n");
printf(" Precmd might not be configured correctly.\n");
kill(child_pid, SIGKILL);
return 1;
}
printf("✓ Received STOP signal from precmd\n\n");
/* Continue the shell - but precmd still has 2 seconds of sleep remaining!
*
* This is the critical test: We received STOP from precmd (MC's synchronization),
* we send CONTINUE, but the precmd hook is STILL RUNNING (it has 2 sec sleep left).
*
* During this time, ZLE is NOT active yet - it only takes over AFTER precmd completes.
*/
printf("Sending CONTINUE...\n");
kill(child_pid, SIGCONT);
child_stopped = 0;
/* Wait 500ms - less than precmd's remaining 2 second sleep
*
* If escape sequences worked during precmd (as Egmont suggested), the handler
* would be called NOW. But it won't be - we'll see the literal "^[t" instead.
*/
printf("Waiting 500ms (precmd still has ~1500ms sleep remaining)...\n");
usleep(500000);
/* Try to send escape sequence NOW - precmd is STILL RUNNING
*
* Expected result: The escape sequence appears literally in output (^[t)
* This PROVES that ZLE is not active while precmd is running.
*/
printf("Sending escape sequence while precmd is STILL running...\n");
write(master_fd, "\033t", 2);
/* Read immediate response (within 500ms - before precmd finishes) */
printf("Reading response immediately (precmd still has ~1500ms left)...\n\n");
usleep(500000); /* 500ms - total elapsed: 1000ms, precmd has 1000ms left */
/* Read output */
char buffer[4096] = {0};
read_available(buffer, sizeof(buffer), 100);
printf("Result:\n");
printf("---\n%s\n---\n\n", buffer);
/* Check result
*
* If we see ">>HANDLER_EXECUTED<<" - ZLE was active (unexpected!)
* If we see "^[t" literally - ZLE was NOT active (expected, proves our point)
*/
if (strstr(buffer, ">>HANDLER_EXECUTED<<"))
{
printf("✗ UNEXPECTED: Handler was executed (precmd must have finished early)\n");
}
else
{
printf("✓ EXPECTED: Handler NOT executed\n");
if (strstr(buffer, "^[t"))
{
printf(" Escape sequence appeared literally (^[t) - ZLE was not active!\n");
}
printf("\nCONCLUSION:\n");
printf("Even after receiving STOP and sending CONTINUE, we cannot send\n");
printf("escape sequences until precmd COMPLETES and returns control to ZLE.\n");
printf("\nThis proves MC cannot send key events from within the precmd hook!\n");
printf("The escape sequence must be sent AFTER precmd finishes and ZLE activates.\n");
}
/* Wait for precmd to complete and any subsequent shell activity */
sleep(10);
kill(child_pid, SIGKILL);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment