fix(prompt): stop a stale render sticking on the prompt - #94
Merged
Conversation
Every background render renamed its output over one shared file. When a slow render outlived a faster one dispatched after it, the slow write landed last and overwrote the fresh content. The generation stamp then refused to apply what it read, so the prompt kept the content it already had until the next command. The stamp stopped stale content being shown, but not fresh content being overwritten. Each job now renders into <tmpfile>.<pid>.part and renames that to <tmpfile>.<pid>, and the signal handler reads only the newest job's file. A job that finishes late writes where nothing reads it, so it can neither be applied nor destroy a newer result, and the generation counter has nothing left to do. Deleting the previous job's files moves into the next job, off the interactive path. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
_tide_repaint tells the next prompt render "this repaint is mine, do not start another render". Both handlers that set it can fire while a command is still running: a background render landing, or a terminal resize. The flag then survived into the next prompt, whose render consumed it and started nothing, so the prompt drawn after the command showed the state from before it ran and stayed that way until the next command. A full-screen program is a good way to hit this, since it runs long and resizes the terminal. Count prompt cycles with fish_postexec and stamp the cycle into the flag. A render still skips its dispatch right after a repaint it asked for itself, so no render loop, and repaints within a cycle still start a render as they did before, which is what keeps the prompt tracking $fish_bind_mode. A flag left over from an earlier cycle no longer matches, so the new prompt renders. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The transient render prints the prompt character and nothing else, so it never reads the rendered prompt. Starting a background render for it spent a fork on output that gets thrown away, and left one more render in flight while the command ran. The prompt drawn after the command starts its own render, so nothing is lost by skipping this one. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The async prompt still showed stale content now and then, once right after a full-screen program exited. Two separate causes, both in
functions/fish_prompt.fish.A fresh render could be destroyed, not just dropped. Every background job renamed its output over one shared file. When a slow render outlived a faster one dispatched after it, the slow write landed last and overwrote the fresh content. The generation stamp added in #91 then refused to apply what it read, so the prompt kept the content it already had until the next command. The stamp stopped stale content being shown, but not fresh content being overwritten, so it replaces that mechanism rather than building on it.
Each job now renders into
<tmpfile>.<pid>.partand renames that to<tmpfile>.<pid>, and the signal handler reads only the newest job's file. A job that finishes late writes where nothing reads it, so it can neither be applied nor destroy a newer result. The generation counter is gone.The repaint flag could leak into the next prompt.
_tide_repainttells the next render "this repaint is mine, do not start another render". Both handlers that set it can fire while a command is still running: a render landing, or a terminal resize. The flag then survived into the next prompt, whose render consumed it and started nothing, so the prompt drawn after the command showed the state from before it ran and stayed that way until the next command. A full-screen program is a good way to hit this, since it runs long and resizes the terminal.Prompt cycles are now counted with
fish_postexecand the cycle is stamped into the flag. A render still skips its dispatch right after a repaint it asked for itself, so no render loop, and repaints within a cycle still start a render, which is what keeps the prompt tracking$fish_bind_mode. A flag left over from an earlier cycle no longer matches.Third commit: the transient render (
--final-rendering) prints the prompt character and nothing else, so it never reads the rendered prompt. It no longer starts a background render — that was a fork spent on output that gets thrown away, and one more render in flight while the command ran.Testing
Four new littlecheck assertions, all of which fail on
mainand pass here:mainboth print the previous command's status, which is the bugPlus one guard for a bug found while writing this: deleting a superseded job's scratch file without killing that job first makes its
mvfail and print to the terminal from a background process. Twenty overlapping dispatches must leave stderr empty.Found by driving a real interactive fish 4.8.1 in a pty and logging every prompt render, dispatch and signal handler run.
mise run lintandmise run testpass at each of the three commits.Co-authored-by: Claude Opus 5 noreply@anthropic.com